feat(healthcare): add support for healthcare source, tool and prebuilt config (#1853)

## Description

Add support for healthcare source, tool and prebuilt config. This branch
consist of all previously approved PRs.

🛠️ Fixes #1648

---------

Co-authored-by: Marwan Tammam <15021613+Quarz0@users.noreply.github.com>
This commit is contained in:
Yuan Teoh
2025-11-06 17:06:04 -08:00
committed by GitHub
parent 22c0b4609d
commit 1f833fb1a1
57 changed files with 8205 additions and 0 deletions

View File

@@ -214,6 +214,29 @@ steps:
dataform \
dataform
- id: "cloud-healthcare"
name: golang:1
waitFor: ["compile-test-binary"]
entrypoint: /bin/bash
env:
- "GOPATH=/gopath"
- "HEALTHCARE_PROJECT=$PROJECT_ID"
- "SERVICE_ACCOUNT_EMAIL=$SERVICE_ACCOUNT_EMAIL"
- "HEALTHCARE_REGION=$_REGION"
- "HEALTHCARE_DATASET=$_HEALTHCARE_DATASET"
- "HEALTHCARE_PREPOPULATED_DICOM_STORE=$_HEALTHCARE_PREPOPULATED_DICOM_STORE"
secretEnv: ["CLIENT_ID"]
volumes:
- name: "go"
path: "/gopath"
args:
- -c
- |
.ci/test_with_coverage.sh \
"Cloud Healthcare API" \
cloudhealthcare \
cloudhealthcare
- id: "postgres"
name: golang:1
waitFor: ["compile-test-binary"]
@@ -937,6 +960,8 @@ substitutions:
_ALLOYDB_AI_NL_CLUSTER: "alloydb-ai-nl-testing"
_ALLOYDB_AI_NL_INSTANCE: "alloydb-ai-nl-testing-instance"
_BIGTABLE_INSTANCE: "bigtable-testing-instance"
_HEALTHCARE_DATASET: "test-dataset"
_HEALTHCARE_PREPOPULATED_DICOM_STORE: "prepopulated-test-dicom-store"
_POSTGRES_HOST: 127.0.0.1
_POSTGRES_PORT: "5432"
_SPANNER_INSTANCE: "spanner-testing"

View File

@@ -69,6 +69,21 @@ import (
_ "github.com/googleapis/genai-toolbox/internal/tools/clickhouse/clickhouselistdatabases"
_ "github.com/googleapis/genai-toolbox/internal/tools/clickhouse/clickhouselisttables"
_ "github.com/googleapis/genai-toolbox/internal/tools/clickhouse/clickhousesql"
_ "github.com/googleapis/genai-toolbox/internal/tools/cloudhealthcare/cloudhealthcarefhirfetchpage"
_ "github.com/googleapis/genai-toolbox/internal/tools/cloudhealthcare/cloudhealthcarefhirpatienteverything"
_ "github.com/googleapis/genai-toolbox/internal/tools/cloudhealthcare/cloudhealthcarefhirpatientsearch"
_ "github.com/googleapis/genai-toolbox/internal/tools/cloudhealthcare/cloudhealthcaregetdataset"
_ "github.com/googleapis/genai-toolbox/internal/tools/cloudhealthcare/cloudhealthcaregetdicomstore"
_ "github.com/googleapis/genai-toolbox/internal/tools/cloudhealthcare/cloudhealthcaregetdicomstoremetrics"
_ "github.com/googleapis/genai-toolbox/internal/tools/cloudhealthcare/cloudhealthcaregetfhirresource"
_ "github.com/googleapis/genai-toolbox/internal/tools/cloudhealthcare/cloudhealthcaregetfhirstore"
_ "github.com/googleapis/genai-toolbox/internal/tools/cloudhealthcare/cloudhealthcaregetfhirstoremetrics"
_ "github.com/googleapis/genai-toolbox/internal/tools/cloudhealthcare/cloudhealthcarelistdicomstores"
_ "github.com/googleapis/genai-toolbox/internal/tools/cloudhealthcare/cloudhealthcarelistfhirstores"
_ "github.com/googleapis/genai-toolbox/internal/tools/cloudhealthcare/cloudhealthcareretrieverendereddicominstance"
_ "github.com/googleapis/genai-toolbox/internal/tools/cloudhealthcare/cloudhealthcaresearchdicominstances"
_ "github.com/googleapis/genai-toolbox/internal/tools/cloudhealthcare/cloudhealthcaresearchdicomseries"
_ "github.com/googleapis/genai-toolbox/internal/tools/cloudhealthcare/cloudhealthcaresearchdicomstudies"
_ "github.com/googleapis/genai-toolbox/internal/tools/cloudmonitoring"
_ "github.com/googleapis/genai-toolbox/internal/tools/cloudsql/cloudsqlcreatedatabase"
_ "github.com/googleapis/genai-toolbox/internal/tools/cloudsql/cloudsqlcreateusers"
@@ -192,6 +207,7 @@ import (
_ "github.com/googleapis/genai-toolbox/internal/sources/bigtable"
_ "github.com/googleapis/genai-toolbox/internal/sources/cassandra"
_ "github.com/googleapis/genai-toolbox/internal/sources/clickhouse"
_ "github.com/googleapis/genai-toolbox/internal/sources/cloudhealthcare"
_ "github.com/googleapis/genai-toolbox/internal/sources/cloudmonitoring"
_ "github.com/googleapis/genai-toolbox/internal/sources/cloudsqladmin"
_ "github.com/googleapis/genai-toolbox/internal/sources/cloudsqlmssql"

View File

@@ -1256,6 +1256,7 @@ func TestPrebuiltTools(t *testing.T) {
cloudsqlmysqlobsvconfig, _ := prebuiltconfigs.Get("cloud-sql-mysql-observability")
cloudsqlmssqlobsvconfig, _ := prebuiltconfigs.Get("cloud-sql-mssql-observability")
serverless_spark_config, _ := prebuiltconfigs.Get("serverless-spark")
cloudhealthcare_config, _ := prebuiltconfigs.Get("cloud-healthcare")
// Set environment variables
t.Setenv("API_KEY", "your_api_key")
@@ -1349,6 +1350,10 @@ func TestPrebuiltTools(t *testing.T) {
t.Setenv("NEO4J_USERNAME", "your_neo4j_user")
t.Setenv("NEO4J_PASSWORD", "your_neo4j_password")
t.Setenv("CLOUD_HEALTHCARE_PROJECT", "your_gcp_project_id")
t.Setenv("CLOUD_HEALTHCARE_REGION", "your_gcp_region")
t.Setenv("CLOUD_HEALTHCARE_DATASET", "your_healthcare_dataset")
ctx, err := testutils.ContextWithNewLogger()
if err != nil {
t.Fatalf("unexpected error: %s", err)
@@ -1628,6 +1633,24 @@ func TestPrebuiltTools(t *testing.T) {
},
},
},
{
name: "cloud healthcare prebuilt tools",
in: cloudhealthcare_config,
wantToolset: server.ToolsetConfigs{
"cloud_healthcare_dataset_tools": tools.ToolsetConfig{
Name: "cloud_healthcare_dataset_tools",
ToolNames: []string{"get_dataset", "list_dicom_stores", "list_fhir_stores"},
},
"cloud_healthcare_fhir_tools": tools.ToolsetConfig{
Name: "cloud_healthcare_fhir_tools",
ToolNames: []string{"get_fhir_store", "get_fhir_store_metrics", "get_fhir_resource", "fhir_patient_search", "fhir_patient_everything", "fhir_fetch_page"},
},
"cloud_healthcare_dicom_tools": tools.ToolsetConfig{
Name: "cloud_healthcare_dicom_tools",
ToolNames: []string{"get_dicom_store", "get_dicom_store_metrics", "search_dicom_studies", "search_dicom_series", "search_dicom_instances", "retrieve_rendered_dicom_instance"},
},
},
},
}
for _, tc := range tcs {

View File

@@ -590,3 +590,33 @@ details on how to connect your AI tools (IDEs) to databases via Toolbox and MCP.
* **Tools:**
* `execute_cypher`: Executes a Cypher query.
* `get_schema`: Retrieves the schema of the Neo4j database.
## Google Cloud Healthcare API
* `--prebuilt` value: `cloud-healthcare`
* **Environment Variables:**
* `CLOUD_HEALTHCARE_PROJECT`: The GCP project ID.
* `CLOUD_HEALTHCARE_REGION`: The Cloud Healthcare API dataset region.
* `CLOUD_HEALTHCARE_DATASET`: The Cloud Healthcare API dataset ID.
* `CLOUD_HEALTHCARE_USE_CLIENT_OAUTH`: (Optional) If `true`, forwards the client's
OAuth access token for authentication. Defaults to `false`.
* **Permissions:**
* **Healthcare FHIR Resource Reader** (`roles/healthcare.fhirResourceReader`) to read an
search FHIR resources.
* **Healthcare DICOM Viewer** (`roles/healthcare.dicomViewer`) to retrieve DICOM images from a
DICOM store.
* **Tools:**
* `get_dataset`: Gets information about a Cloud Healthcare API dataset.
* `list_dicom_stores`: Lists DICOM stores in a Cloud Healthcare API dataset.
* `list_fhir_stores`: Lists FHIR stores in a Cloud Healthcare API dataset.
* `get_fhir_store`: Gets information about a FHIR store.
* `get_fhir_store_metrics`: Gets metrics for a FHIR store.
* `get_fhir_resource`: Gets a FHIR resource from a FHIR store.
* `fhir_patient_search`: Searches for patient resource(s) based on a set of criteria.
* `fhir_patient_everything`: Retrieves resources related to a given patient.
* `fhir_fetch_page`: Fetches a page of FHIR resources.
* `get_dicom_store`: Gets information about a DICOM store.
* `get_dicom_store_metrics`: Gets metrics for a DICOM store.
* `search_dicom_studies`: Searches for DICOM studies.
* `search_dicom_series`: Searches for DICOM series.
* `search_dicom_instances`: Searches for DICOM instances.
* `retrieve_rendered_dicom_instance`: Retrieves a rendered DICOM instance.

View File

@@ -0,0 +1,164 @@
---
title: "Cloud Healthcare API"
linkTitle: "Cloud Healthcare"
type: docs
weight: 1
description: >
The Cloud Healthcare API provides a managed solution for storing and
accessing healthcare data in Google Cloud, providing a critical bridge
between existing care systems and applications hosted on Google Cloud.
---
## About
The [Cloud Healthcare API][healthcare-docs] provides a managed solution
for storing and accessing healthcare data in Google Cloud, providing a
critical bridge between existing care systems and applications hosted on
Google Cloud. It supports healthcare data standards such as HL7® FHIR®,
HL7® v2, and DICOM®. It provides a fully managed, highly scalable,
enterprise-grade development environment for building clinical and analytics
solutions securely on Google Cloud.
A dataset is a container in your Google Cloud project that holds modality-specific
healthcare data. Datasets contain other data stores, such as FHIR stores and DICOM
stores, which in turn hold their own types of healthcare data.
A single dataset can contain one or many data stores, and those stores can all service
the same modality or different modalities as application needs dictate. Using multiple
stores in the same dataset might be appropriate in various situations.
If you are new to the Cloud Healthcare API, you can try to
[create and view datasets and stores using curl][healthcare-quickstart-curl].
[healthcare-docs]: https://cloud.google.com/healthcare/docs
[healthcare-quickstart-curl]:
https://cloud.google.com/healthcare-api/docs/store-healthcare-data-rest
## Available Tools
- [`cloud-healthcare-get-dataset`](../tools/cloudhealthcare/cloud-healthcare-get-dataset.md)
Retrieves a datasets details.
- [`cloud-healthcare-list-fhir-stores`](../tools/cloudhealthcare/cloud-healthcare-list-fhir-stores.md)
Lists the available FHIR stores in the healthcare dataset.
- [`cloud-healthcare-list-dicom-stores`](../tools/cloudhealthcare/cloud-healthcare-list-dicom-stores.md)
Lists the available DICOM stores in the healthcare dataset.
- [`cloud-healthcare-get-fhir-store`](../tools/cloudhealthcare/cloud-healthcare-get-fhir-store.md)
Retrieves information about a FHIR store.
- [`cloud-healthcare-get-fhir-store-metrics`](../tools/cloudhealthcare/cloud-healthcare-get-fhir-store-metrics.md)
Retrieves metrics for a FHIR store.
- [`cloud-healthcare-get-fhir-resource`](../tools/cloudhealthcare/cloud-healthcare-get-fhir-resource.md)
Retrieves a specific FHIR resource from a FHIR store.
- [`cloud-healthcare-fhir-patient-search`](../tools/cloudhealthcare/cloud-healthcare-fhir-patient-search.md)
Searches for patients in a FHIR store based on a set of criteria.
- [`cloud-healthcare-fhir-patient-everything`](../tools/cloudhealthcare/cloud-healthcare-fhir-patient-everything.md)
Retrieves all information for a given patient.
- [`cloud-healthcare-fhir-fetch-page`](../tools/cloudhealthcare/cloud-healthcare-fhir-fetch-page.md)
Fetches a page of FHIR resources from a given URL.
- [`cloud-healthcare-get-dicom-store`](../tools/cloudhealthcare/cloud-healthcare-get-dicom-store.md)
Retrieves information about a DICOM store.
- [`cloud-healthcare-get-dicom-store-metrics`](../tools/cloudhealthcare/cloud-healthcare-get-dicom-store-metrics.md)
Retrieves metrics for a DICOM store.
- [`cloud-healthcare-search-dicom-studies`](../tools/cloudhealthcare/cloud-healthcare-search-dicom-studies.md)
Searches for DICOM studies in a DICOM store.
- [`cloud-healthcare-search-dicom-series`](../tools/cloudhealthcare/cloud-healthcare-search-dicom-series.md)
Searches for DICOM series in a DICOM store.
- [`cloud-healthcare-search-dicom-instances`](../tools/cloudhealthcare/cloud-healthcare-search-dicom-instances.md)
Searches for DICOM instances in a DICOM store.
- [`cloud-healthcare-retrieve-rendered-dicom-instance`](../tools/cloudhealthcare/cloud-healthcare-retrieve-rendered-dicom-instance.md)
Retrieves a rendered DICOM instance from a DICOM store.
## Requirements
### IAM Permissions
The Cloud Healthcare API uses [Identity and Access Management (IAM)][iam-overview] to control
user and group access to Cloud Healthcare resources like projects, datasets, and stores.
### Authentication via Application Default Credentials (ADC)
By **default**, Toolbox will use your [Application Default Credentials
(ADC)][adc] to authorize and authenticate when interacting with the
[Cloud Healthcare API][healthcare-docs].
When using this method, you need to ensure the IAM identity associated with your
ADC (such as a service account) has the correct permissions for the queries you
intend to run. Common roles include `roles/healthcare.fhirResourceReader` (which includes
permissions to read and search for FHIR resources) or `roles/healthcare.dicomViewer` (for
retrieving DICOM images).
Follow this [guide][set-adc] to set up your ADC.
### Authentication via User's OAuth Access Token
If the `useClientOAuth` parameter is set to `true`, Toolbox will instead use the
OAuth access token for authentication. This token is parsed from the
`Authorization` header passed in with the tool invocation request. This method
allows Toolbox to make queries to the [Cloud Healthcare API][healthcare-docs] on behalf of the
client or the end-user.
When using this on-behalf-of authentication, you must ensure that the
identity used has been granted the correct IAM permissions.
[iam-overview]: <https://cloud.google.com/healthcare/docs/access-control>
[adc]: <https://cloud.google.com/docs/authentication#adc>
[set-adc]: <https://cloud.google.com/docs/authentication/provide-credentials-adc>
## Example
Initialize a Cloud Healthcare API source that uses ADC:
```yaml
sources:
my-healthcare-source:
kind: "cloud-healthcare"
project: "my-project-id"
region: "us-central1"
dataset: "my-healthcare-dataset-id"
# allowedFhirStores: # Optional: Restricts tool access to a specific list of FHIR store IDs.
# - "my_fhir_store_1"
# allowedDicomStores: # Optional: Restricts tool access to a specific list of DICOM store IDs.
# - "my_dicom_store_1"
# - "my_dicom_store_2"
```
Initialize a Cloud Healthcare API source that uses the client's access token:
```yaml
sources:
my-healthcare-client-auth-source:
kind: "cloud-healthcare"
project: "my-project-id"
region: "us-central1"
dataset: "my-healthcare-dataset-id"
useClientOAuth: true
# allowedFhirStores: # Optional: Restricts tool access to a specific list of FHIR store IDs.
# - "my_fhir_store_1"
# allowedDicomStores: # Optional: Restricts tool access to a specific list of DICOM store IDs.
# - "my_dicom_store_1"
# - "my_dicom_store_2"
```
## Reference
| **field** | **type** | **required** | **description** |
|--------------------|:--------:|:------------:|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| kind | string | true | Must be "cloud-healthcare". |
| project | string | true | ID of the GCP project that the dataset lives in. |
| region | string | true | Specifies the region (e.g., 'us', 'asia-northeast1') of the healthcare dataset. [Learn More](https://cloud.google.com/healthcare-api/docs/regions) |
| dataset | string | true | ID of the healthcare dataset. |
| allowedFhirStores | []string | false | An optional list of FHIR store IDs that tools using this source are allowed to access. If provided, any tool operation attempting to access a store not in this list will be rejected. If a single store is provided, it will be treated as the default for prebuilt tools. |
| allowedDicomStores | []string | false | An optional list of DICOM store IDs that tools using this source are allowed to access. If provided, any tool operation attempting to access a store not in this list will be rejected. If a single store is provided, it will be treated as the default for prebuilt tools. |
| useClientOAuth | bool | false | If true, forwards the client's OAuth access token from the "Authorization" header to downstream queries. |

View File

@@ -0,0 +1,8 @@
---
title: "Cloud Healthcare API"
linkTitle: "Cloud Healthcare"
type: docs
weight: 1
description: >
Tools that work with Cloud Healthcare Sources.
---

View File

@@ -0,0 +1,43 @@
---
title: "cloud-healthcare-fhir-fetch-page"
type: docs
weight: 1
description: >
A "cloud-healthcare-fhir-fetch-page" tool fetches a page of FHIR resources from a given URL.
aliases:
- /resources/tools/cloud-healthcare-fhir-fetch-page
---
## About
A `cloud-healthcare-fhir-fetch-page` tool fetches a page of FHIR resources from a given URL. It's
compatible with the following sources:
- [cloud-healthcare](../../sources/cloud-healthcare.md)
`cloud-healthcare-fhir-fetch-page` can be used for pagination when a previous tool call (like
`cloud-healthcare-fhir-patient-search` or `cloud-healthcare-fhir-patient-everything`) returns a 'next' link in the response bundle.
## Example
```yaml
tools:
get_fhir_store:
kind: cloud-healthcare-fhir-fetch-page
source: my-healthcare-source
description: Use this tool to fetch a page of FHIR resources from a FHIR Bundle's entry.link.url
```
## Reference
| **field** | **type** | **required** | **description** |
|-------------|:--------:|:------------:|----------------------------------------------------|
| kind | string | true | Must be "cloud-healthcare-fhir-fetch-page". |
| source | string | true | Name of the healthcare source. |
| description | string | true | Description of the tool that is passed to the LLM. |
### Parameters
| **field** | **type** | **required** | **description** |
|-----------|:--------:|:------------:|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| pageURL | string | true | The full URL of the FHIR page to fetch. This would usually be the value of `Bundle.entry.link.url` field within the response returned from FHIR search or FHIR patient everything operations. |

View File

@@ -0,0 +1,49 @@
---
title: "cloud-healthcare-fhir-patient-everything"
type: docs
weight: 1
description: >
A "cloud-healthcare-fhir-patient-everything" tool retrieves all information for a given patient.
aliases:
- /resources/tools/cloud-healthcare-fhir-patient-everything
---
## About
A `cloud-healthcare-fhir-patient-everything` tool retrieves resources related to a given patient
from a FHIR store. It's compatible with the following sources:
- [cloud-healthcare](../../sources/cloud-healthcare.md)
`cloud-healthcare-fhir-patient-everything` returns all the information available for a given
patient ID. It can be configured to only return certain resource types, or only
resources that have been updated after a given time.
## Example
```yaml
tools:
fhir_patient_everything:
kind: cloud-healthcare-fhir-patient-everything
source: my-healthcare-source
description: Use this tool to retrieve all the information about a given patient.
```
## Reference
| **field** | **type** | **required** | **description** |
|-------------|:--------:|:------------:|-----------------------------------------------------|
| kind | string | true | Must be "cloud-healthcare-fhir-patient-everything". |
| source | string | true | Name of the healthcare source. |
| description | string | true | Description of the tool that is passed to the LLM. |
### Parameters
| **field** | **type** | **required** | **description** |
|---------------------|:--------:|:------------:|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| patientID | string | true | The ID of the patient FHIR resource for which the information is required. |
| resourceTypesFilter | string | false | String of comma-delimited FHIR resource types. If provided, only resources of the specified resource type(s) are returned. |
| sinceFilter | string | false | If provided, only resources updated after this time are returned. The time uses the format YYYY-MM-DDThh:mm:ss.sss+zz:zz. The time must be specified to the second and include a time zone. For example, 2015-02-07T13:28:17.239+02:00 or 2017-01-01T00:00:00Z. |
| storeID | string | true* | The FHIR store ID to search in. |
*If the `allowedFHIRStores` in the source has length 1, then the `storeID` parameter is not needed.

View File

@@ -0,0 +1,63 @@
---
title: "cloud-healthcare-fhir-patient-search"
type: docs
weight: 1
description: >
A "cloud-healthcare-fhir-patient-search" tool searches for patients in a FHIR store.
aliases:
- /resources/tools/cloud-healthcare-fhir-patient-search
---
## About
A `cloud-healthcare-fhir-patient-search` tool searches for patients in a FHIR store based on a
set of criteria. It's compatible with the following sources:
- [cloud-healthcare](../../sources/cloud-healthcare.md)
`cloud-healthcare-fhir-patient-search` returns a list of patients that match the given criteria.
## Example
```yaml
tools:
fhir_patient_search:
kind: cloud-healthcare-fhir-patient-search
source: my-healthcare-source
description: Use this tool to search for patients in the FHIR store.
```
## Reference
| **field** | **type** | **required** | **description** |
|-------------|:--------:|:------------:|----------------------------------------------------|
| kind | string | true | Must be "cloud-healthcare-fhir-patient-search". |
| source | string | true | Name of the healthcare source. |
| description | string | true | Description of the tool that is passed to the LLM. |
### Parameters
| **field** | **type** | **required** | **description** |
|------------------|:--------:|:------------:|--------------------------------------------------------------------------------|
| active | string | false | Whether the patient record is active. |
| city | string | false | The city of the patient's address. |
| country | string | false | The country of the patient's address. |
| postalcode | string | false | The postal code of the patient's address. |
| state | string | false | The state of the patient's address. |
| addressSubstring | string | false | A substring to search for in any address field. |
| birthDateRange | string | false | A date range for the patient's birth date in the format YYYY-MM-DD/YYYY-MM-DD. |
| deathDateRange | string | false | A date range for the patient's death date in the format YYYY-MM-DD/YYYY-MM-DD. |
| deceased | string | false | Whether the patient is deceased. |
| email | string | false | The patient's email address. |
| gender | string | false | The patient's gender. |
| addressUse | string | false | The use of the patient's address. |
| name | string | false | The patient's name. |
| givenName | string | false | A portion of the given name of the patient. |
| familyName | string | false | A portion of the family name of the patient. |
| phone | string | false | The patient's phone number. |
| language | string | false | The patient's preferred language. |
| identifier | string | false | An identifier for the patient. |
| summary | boolean | false | Requests the server to return a subset of the resource. True by default. |
| storeID | string | true* | The FHIR store ID to search in. |
*If the `allowedFHIRStores` in the source has length 1, then the `storeID` parameter is not needed.

View File

@@ -0,0 +1,37 @@
---
title: "cloud-healthcare-get-dataset"
type: docs
weight: 1
description: >
A "cloud-healthcare-get-dataset" tool retrieves metadata for the Healthcare dataset in the source.
aliases:
- /resources/tools/cloud-healthcare-get-dataset
---
## About
A `cloud-healthcare-get-dataset` tool retrieves metadata for a Healthcare dataset.
It's compatible with the following sources:
- [cloud-healthcare](../../sources/cloud-healthcare.md)
`cloud-healthcare-get-dataset` returns the metadata of the healthcare dataset
configured in the source. It takes no extra parameters.
## Example
```yaml
tools:
get_dataset:
kind: cloud-healthcare-get-dataset
source: my-healthcare-source
description: Use this tool to get healthcare dataset metadata.
```
## Reference
| **field** | **type** | **required** | **description** |
|-------------|:------------------------------------------:|:------------:|----------------------------------------------------|
| kind | string | true | Must be "cloud-healthcare-get-dataset". |
| source | string | true | Name of the healthcare source. |
| description | string | true | Description of the tool that is passed to the LLM. |

View File

@@ -0,0 +1,44 @@
---
title: "cloud-healthcare-get-dicom-store-metrics"
type: docs
weight: 1
description: >
A "cloud-healthcare-get-dicom-store-metrics" tool retrieves metrics for a DICOM store.
aliases:
- /resources/tools/cloud-healthcare-get-dicom-store-metrics
---
## About
A `cloud-healthcare-get-dicom-store-metrics` tool retrieves metrics for a DICOM store. It's
compatible with the following sources:
- [cloud-healthcare](../../sources/cloud-healthcare.md)
`cloud-healthcare-get-dicom-store-metrics` returns the metrics of a DICOM store.
## Example
```yaml
tools:
get_dicom_store_metrics:
kind: cloud-healthcare-get-dicom-store-metrics
source: my-healthcare-source
description: Use this tool to get metrics for a DICOM store.
```
## Reference
| **field** | **type** | **required** | **description** |
|-------------|:--------:|:------------:|-----------------------------------------------------|
| kind | string | true | Must be "cloud-healthcare-get-dicom-store-metrics". |
| source | string | true | Name of the healthcare source. |
| description | string | true | Description of the tool that is passed to the LLM. |
### Parameters
| **field** | **type** | **required** | **description** |
|-----------|:--------:|:------------:|----------------------------------------|
| storeID | string | true* | The DICOM store ID to get metrics for. |
*If the `allowedDICOMStores` in the source has length 1, then the `storeID` parameter is not needed.

View File

@@ -0,0 +1,44 @@
---
title: "cloud-healthcare-get-dicom-store"
type: docs
weight: 1
description: >
A "cloud-healthcare-get-dicom-store" tool retrieves information about a DICOM store.
aliases:
- /resources/tools/cloud-healthcare-get-dicom-store
---
## About
A `cloud-healthcare-get-dicom-store` tool retrieves information about a DICOM store. It's
compatible with the following sources:
- [cloud-healthcare](../../sources/cloud-healthcare.md)
`cloud-healthcare-get-dicom-store` returns the details of a DICOM store.
## Example
```yaml
tools:
get_dicom_store:
kind: cloud-healthcare-get-dicom-store
source: my-healthcare-source
description: Use this tool to get information about a DICOM store.
```
## Reference
| **field** | **type** | **required** | **description** |
|-------------|:--------:|:------------:|----------------------------------------------------|
| kind | string | true | Must be "cloud-healthcare-get-dicom-store". |
| source | string | true | Name of the healthcare source. |
| description | string | true | Description of the tool that is passed to the LLM. |
### Parameters
| **field** | **type** | **required** | **description** |
|-----------|:--------:|:------------:|----------------------------------------|
| storeID | string | true* | The DICOM store ID to get details for. |
*If the `allowedDICOMStores` in the source has length 1, then the `storeID` parameter is not needed.

View File

@@ -0,0 +1,47 @@
---
title: "cloud-healthcare-get-fhir-resource"
linkTitle: "cloud-healthcare-get-fhir-resource"
type: docs
weight: 1
description: >
A "cloud-healthcare-get-fhir-resource" tool retrieves a specific FHIR resource.
aliases:
- /resources/tools/cloud-healthcare-get-fhir-resource
---
## About
A `cloud-healthcare-get-fhir-resource` tool retrieves a specific FHIR resource from a FHIR store.
It's compatible with the following sources:
- [cloud-healthcare](../../sources/cloud-healthcare.md)
`cloud-healthcare-get-fhir-resource` returns a single FHIR resource, identified by its type and ID.
## Example
```yaml
tools:
get_fhir_resource:
kind: cloud-healthcare-get-fhir-resource
source: my-healthcare-source
description: Use this tool to retrieve a specific FHIR resource.
```
## Reference
| **field** | **type** | **required** | **description** |
|-------------|:--------:|:------------:|----------------------------------------------------|
| kind | string | true | Must be "cloud-healthcare-get-fhir-resource". |
| source | string | true | Name of the healthcare source. |
| description | string | true | Description of the tool that is passed to the LLM. |
### Parameters
| **field** | **type** | **required** | **description** |
|--------------|:--------:|:------------:|------------------------------------------------------------------|
| resourceType | string | true | The FHIR resource type to retrieve (e.g., Patient, Observation). |
| resourceID | string | true | The ID of the FHIR resource to retrieve. |
| storeID | string | true* | The FHIR store ID to retrieve the resource from. |
*If the `allowedFHIRStores` in the source has length 1, then the `storeID` parameter is not needed.

View File

@@ -0,0 +1,44 @@
---
title: "cloud-healthcare-get-fhir-store-metrics"
type: docs
weight: 1
description: >
A "cloud-healthcare-get-fhir-store-metrics" tool retrieves metrics for a FHIR store.
aliases:
- /resources/tools/cloud-healthcare-get-fhir-store-metrics
---
## About
A `cloud-healthcare-get-fhir-store-metrics` tool retrieves metrics for a FHIR store. It's
compatible with the following sources:
- [cloud-healthcare](../../sources/cloud-healthcare.md)
`cloud-healthcare-get-fhir-store-metrics` returns the metrics of a FHIR store.
## Example
```yaml
tools:
get_fhir_store_metrics:
kind: cloud-healthcare-get-fhir-store-metrics
source: my-healthcare-source
description: Use this tool to get metrics for a FHIR store.
```
## Reference
| **field** | **type** | **required** | **description** |
|-------------|:--------:|:------------:|----------------------------------------------------|
| kind | string | true | Must be "cloud-healthcare-get-fhir-store-metrics". |
| source | string | true | Name of the healthcare source. |
| description | string | true | Description of the tool that is passed to the LLM. |
### Parameters
| **field** | **type** | **required** | **description** |
|-----------|:--------:|:------------:|---------------------------------------|
| storeID | string | true* | The FHIR store ID to get metrics for. |
*If the `allowedFHIRStores` in the source has length 1, then the `storeID` parameter is not needed.

View File

@@ -0,0 +1,44 @@
---
title: "cloud-healthcare-get-fhir-store"
type: docs
weight: 1
description: >
A "cloud-healthcare-get-fhir-store" tool retrieves information about a FHIR store.
aliases:
- /resources/tools/cloud-healthcare-get-fhir-store
---
## About
A `cloud-healthcare-get-fhir-store` tool retrieves information about a FHIR store. It's
compatible with the following sources:
- [cloud-healthcare](../../sources/cloud-healthcare.md)
`cloud-healthcare-get-fhir-store` returns the details of a FHIR store.
## Example
```yaml
tools:
get_fhir_store:
kind: cloud-healthcare-get-fhir-store
source: my-healthcare-source
description: Use this tool to get information about a FHIR store.
```
## Reference
| **field** | **type** | **required** | **description** |
|-------------|:--------:|:------------:|----------------------------------------------------|
| kind | string | true | Must be "cloud-healthcare-get-fhir-store". |
| source | string | true | Name of the healthcare source. |
| description | string | true | Description of the tool that is passed to the LLM. |
### Parameters
| **field** | **type** | **required** | **description** |
|-----------|:--------:|:------------:|---------------------------------------|
| storeID | string | true* | The FHIR store ID to get details for. |
*If the `allowedFHIRStores` in the source has length 1, then the `storeID` parameter is not needed.

View File

@@ -0,0 +1,37 @@
---
title: "cloud-healthcare-list-dicom-stores"
type: docs
weight: 1
description: >
A "cloud-healthcare-list-dicom-stores" lists the available DICOM stores in the healthcare dataset.
aliases:
- /resources/tools/cloud-healthcare-list-dicom-stores
---
## About
A `cloud-healthcare-list-dicom-stores` lists the available DICOM stores in the healthcare dataset.
It's compatible with the following sources:
- [cloud-healthcare](../../sources/cloud-healthcare.md)
`cloud-healthcare-list-dicom-stores` returns the details of the available DICOM stores in the
dataset of the healthcare source. It takes no extra parameters.
## Example
```yaml
tools:
list_dicom_stores:
kind: cloud-healthcare-list-dicom-stores
source: my-healthcare-source
description: Use this tool to list DICOM stores in the healthcare dataset.
```
## Reference
| **field** | **type** | **required** | **description** |
|-------------|:--------:|:------------:|----------------------------------------------------|
| kind | string | true | Must be "cloud-healthcare-list-dicom-stores". |
| source | string | true | Name of the healthcare source. |
| description | string | true | Description of the tool that is passed to the LLM. |

View File

@@ -0,0 +1,37 @@
---
title: "cloud-healthcare-list-fhir-stores"
type: docs
weight: 1
description: >
A "cloud-healthcare-list-fhir-stores" lists the available FHIR stores in the healthcare dataset.
aliases:
- /resources/tools/cloud-healthcare-list-fhir-stores
---
## About
A `cloud-healthcare-list-fhir-stores` lists the available FHIR stores in the healthcare dataset.
It's compatible with the following sources:
- [cloud-healthcare](../../sources/cloud-healthcare.md)
`cloud-healthcare-list-fhir-stores` returns the details of the available FHIR stores in the
dataset of the healthcare source. It takes no extra parameters.
## Example
```yaml
tools:
list_fhir_stores:
kind: cloud-healthcare-list-fhir-stores
source: my-healthcare-source
description: Use this tool to list FHIR stores in the healthcare dataset.
```
## Reference
| **field** | **type** | **required** | **description** |
|-------------|:--------:|:------------:|----------------------------------------------------|
| kind | string | true | Must be "cloud-healthcare-list-fhir-stores". |
| source | string | true | Name of the healthcare source. |
| description | string | true | Description of the tool that is passed to the LLM. |

View File

@@ -0,0 +1,48 @@
---
title: "cloud-healthcare-retrieve-rendered-dicom-instance"
type: docs
weight: 1
description: >
A "cloud-healthcare-retrieve-rendered-dicom-instance" tool retrieves a rendered DICOM instance from a DICOM store.
aliases:
- /resources/tools/cloud-healthcare-retrieve-rendered-dicom-instance
---
## About
A `cloud-healthcare-retrieve-rendered-dicom-instance` tool retrieves a rendered DICOM instance from a DICOM store.
It's compatible with the following sources:
- [cloud-healthcare](../../sources/cloud-healthcare.md)
`cloud-healthcare-retrieve-rendered-dicom-instance` returns a base64 encoded string of the image in JPEG format.
## Example
```yaml
tools:
retrieve_rendered_dicom_instance:
kind: cloud-healthcare-retrieve-rendered-dicom-instance
source: my-healthcare-source
description: Use this tool to retrieve a rendered DICOM instance from the DICOM store.
```
## Reference
| **field** | **type** | **required** | **description** |
|-------------|:--------:|:------------:|--------------------------------------------------------------|
| kind | string | true | Must be "cloud-healthcare-retrieve-rendered-dicom-instance". |
| source | string | true | Name of the healthcare source. |
| description | string | true | Description of the tool that is passed to the LLM. |
### Parameters
| **field** | **type** | **required** | **description** |
|-------------------|:--------:|:------------:|--------------------------------------------------------------------------------------------------|
| StudyInstanceUID | string | true | The UID of the DICOM study. |
| SeriesInstanceUID | string | true | The UID of the DICOM series. |
| SOPInstanceUID | string | true | The UID of the SOP instance. |
| FrameNumber | integer | false | The frame number to retrieve (1-based). Only applicable to multi-frame instances. Defaults to 1. |
| storeID | string | true* | The DICOM store ID to retrieve from. |
*If the `allowedDICOMStores` in the source has length 1, then the `storeID` parameter is not needed.

View File

@@ -0,0 +1,55 @@
---
title: "cloud-healthcare-search-dicom-instances"
type: docs
weight: 1
description: >
A "cloud-healthcare-search-dicom-instances" tool searches for DICOM instances in a DICOM store.
aliases:
- /resources/tools/cloud-healthcare-search-dicom-instances
---
## About
A `cloud-healthcare-search-dicom-instances` tool searches for DICOM instances in a DICOM store based on a
set of criteria. It's compatible with the following sources:
- [cloud-healthcare](../../sources/cloud-healthcare.md)
`search-dicom-instances` returns a list of DICOM instances that match the given criteria.
## Example
```yaml
tools:
search_dicom_instances:
kind: cloud-healthcare-search-dicom-instances
source: my-healthcare-source
description: Use this tool to search for DICOM instances in the DICOM store.
```
## Reference
| **field** | **type** | **required** | **description** |
|-------------|:--------:|:------------:|----------------------------------------------------|
| kind | string | true | Must be "cloud-healthcare-search-dicom-instances". |
| source | string | true | Name of the healthcare source. |
| description | string | true | Description of the tool that is passed to the LLM. |
### Parameters
| **field** | **type** | **required** | **description** |
|------------------------|:--------:|:------------:|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| StudyInstanceUID | string | false | The UID of the DICOM study. |
| PatientName | string | false | The name of the patient. |
| PatientID | string | false | The ID of the patient. |
| AccessionNumber | string | false | The accession number of the study. |
| ReferringPhysicianName | string | false | The name of the referring physician. |
| StudyDate | string | false | The date of the study in the format `YYYYMMDD`. You can also specify a date range in the format `YYYYMMDD-YYYYMMDD`. |
| SeriesInstanceUID | string | false | The UID of the DICOM series. |
| Modality | string | false | The modality of the series. |
| SOPInstanceUID | string | false | The UID of the SOP instance. |
| fuzzymatching | boolean | false | Whether to enable fuzzy matching for patient names. Fuzzy matching will perform tokenization and normalization of both the value of PatientName in the query and the stored value. It will match if any search token is a prefix of any stored token. For example, if PatientName is "John^Doe", then "jo", "Do" and "John Doe" will all match. However "ohn" will not match. |
| includefield | []string | false | List of attributeIDs to include in the output, such as DICOM tag IDs or keywords. Set to `["all"]` to return all available tags. |
| storeID | string | true* | The DICOM store ID to search in. |
*If the `allowedDICOMStores` in the source has length 1, then the `storeID` parameter is not needed.

View File

@@ -0,0 +1,54 @@
---
title: "cloud-healthcare-search-dicom-series"
type: docs
weight: 1
description: >
A "cloud-healthcare-search-dicom-series" tool searches for DICOM series in a DICOM store.
aliases:
- /resources/tools/cloud-healthcare-search-dicom-series
---
## About
A `cloud-healthcare-search-dicom-series` tool searches for DICOM series in a DICOM store based on a
set of criteria. It's compatible with the following sources:
- [cloud-healthcare](../../sources/cloud-healthcare.md)
`cloud-healthcare-search-dicom-series` returns a list of DICOM series that match the given criteria.
## Example
```yaml
tools:
search_dicom_series:
kind: cloud-healthcare-search-dicom-series
source: my-healthcare-source
description: Use this tool to search for DICOM series in the DICOM store.
```
## Reference
| **field** | **type** | **required** | **description** |
|-------------|:--------:|:------------:|----------------------------------------------------|
| kind | string | true | Must be "cloud-healthcare-search-dicom-series". |
| source | string | true | Name of the healthcare source. |
| description | string | true | Description of the tool that is passed to the LLM. |
### Parameters
| **field** | **type** | **required** | **description** |
|------------------------|:--------:|:------------:|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| StudyInstanceUID | string | false | The UID of the DICOM study. |
| PatientName | string | false | The name of the patient. |
| PatientID | string | false | The ID of the patient. |
| AccessionNumber | string | false | The accession number of the study. |
| ReferringPhysicianName | string | false | The name of the referring physician. |
| StudyDate | string | false | The date of the study in the format `YYYYMMDD`. You can also specify a date range in the format `YYYYMMDD-YYYYMMDD`. |
| SeriesInstanceUID | string | false | The UID of the DICOM series. |
| Modality | string | false | The modality of the series. |
| fuzzymatching | boolean | false | Whether to enable fuzzy matching for patient names. Fuzzy matching will perform tokenization and normalization of both the value of PatientName in the query and the stored value. It will match if any search token is a prefix of any stored token. For example, if PatientName is "John^Doe", then "jo", "Do" and "John Doe" will all match. However "ohn" will not match. |
| includefield | []string | false | List of attributeIDs to include in the output, such as DICOM tag IDs or keywords. Set to `["all"]` to return all available tags. |
| storeID | string | true* | The DICOM store ID to search in. |
*If the `allowedDIComStores` in the source has length 1, then the `storeID` parameter is not needed.

View File

@@ -0,0 +1,52 @@
---
title: "cloud-healthcare-search-dicom-studies"
type: docs
weight: 1
description: >
A "cloud-healthcare-search-dicom-studies" tool searches for DICOM studies in a DICOM store.
aliases:
- /resources/tools/cloud-healthcare-healthcare-search-dicom-studies
---
## About
A `cloud-healthcare-search-dicom-studies` tool searches for DICOM studies in a DICOM store based on a
set of criteria. It's compatible with the following sources:
- [cloud-healthcare](../../sources/cloud-healthcare.md)
`cloud-healthcare-search-dicom-studies` returns a list of DICOM studies that match the given criteria.
## Example
```yaml
tools:
search_dicom_studies:
kind: cloud-healthcare-search-dicom-studies
source: my-healthcare-source
description: Use this tool to search for DICOM studies in the DICOM store.
```
## Reference
| **field** | **type** | **required** | **description** |
|-------------|:--------:|:------------:|----------------------------------------------------|
| kind | string | true | Must be "cloud-healthcare-search-dicom-studies". |
| source | string | true | Name of the healthcare source. |
| description | string | true | Description of the tool that is passed to the LLM. |
### Parameters
| **field** | **type** | **required** | **description** |
|------------------------|:--------:|:------------:|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| StudyInstanceUID | string | false | The UID of the DICOM study. |
| PatientName | string | false | The name of the patient. |
| PatientID | string | false | The ID of the patient. |
| AccessionNumber | string | false | The accession number of the study. |
| ReferringPhysicianName | string | false | The name of the referring physician. |
| StudyDate | string | false | The date of the study in the format `YYYYMMDD`. You can also specify a date range in the format `YYYYMMDD-YYYYMMDD`. |
| fuzzymatching | boolean | false | Whether to enable fuzzy matching for patient names. Fuzzy matching will perform tokenization and normalization of both the value of PatientName in the query and the stored value. It will match if any search token is a prefix of any stored token. For example, if PatientName is "John^Doe", then "jo", "Do" and "John Doe" will all match. However "ohn" will not match. |
| includefield | []string | false | List of attributeIDs to include in the output, such as DICOM tag IDs or keywords. Set to `["all"]` to return all available tags. |
| storeID | string | true* | The DICOM store ID to search in. |
*If the `allowedDICOMStores` in the source has length 1, then the `storeID` parameter is not needed.

View File

@@ -26,6 +26,7 @@ var expectedToolSources = []string{
"alloydb-postgres",
"bigquery",
"clickhouse",
"cloud-healthcare",
"cloud-sql-mssql-admin",
"cloud-sql-mssql-observability",
"cloud-sql-mssql",
@@ -125,6 +126,7 @@ func TestGetPrebuiltTool(t *testing.T) {
mindsdb_config, _ := Get("mindsdb")
sqlite_config, _ := Get("sqlite")
neo4jconfig, _ := Get("neo4j")
healthcare_config, _ := Get("cloud-healthcare")
if len(alloydb_admin_config) <= 0 {
t.Fatalf("unexpected error: could not fetch alloydb prebuilt tools yaml")
@@ -213,6 +215,9 @@ func TestGetPrebuiltTool(t *testing.T) {
if len(neo4jconfig) <= 0 {
t.Fatalf("unexpected error: could not fetch neo4j prebuilt tools yaml")
}
if len(healthcare_config) <= 0 {
t.Fatalf("unexpected error: could not fetch healthcare prebuilt tools yaml")
}
}
func TestFailGetPrebuiltTool(t *testing.T) {

View File

@@ -0,0 +1,103 @@
# 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.
sources:
healthcare-source:
kind: cloud-healthcare
project: ${CLOUD_HEALTHCARE_PROJECT}
region: ${CLOUD_HEALTHCARE_REGION}
dataset: ${CLOUD_HEALTHCARE_DATASET}
useClientOAuth: ${CLOUD_HEALTHCARE_USE_CLIENT_OAUTH:false}
tools:
get_dataset:
kind: cloud-healthcare-get-dataset
description: Use this tool to get the details of a healthcare dataset
source: healthcare-source
list_dicom_stores:
kind: cloud-healthcare-list-dicom-stores
description: Use this tool to list the DICOM stores in the healthcare dataset
source: healthcare-source
list_fhir_stores:
kind: cloud-healthcare-list-fhir-stores
description: Use this tool to list the FHIR stores in the healthcare dataset
source: healthcare-source
get_fhir_store:
kind: cloud-healthcare-get-fhir-store
description: Use this tool to get details about a FHIR store in the healthcare dataset
source: healthcare-source
get_fhir_store_metrics:
kind: cloud-healthcare-get-fhir-store-metrics
description: Use this tool to get metrics about a FHIR store in the healthcare dataset
source: healthcare-source
get_fhir_resource:
kind: cloud-healthcare-get-fhir-resource
description: Use this tool to get a FHIR resource from a FHIR store
source: healthcare-source
fhir_patient_search:
kind: cloud-healthcare-fhir-patient-search
description: Use this tool to search for patient resource(s) in a FHIR store based on a set of criteria
source: healthcare-source
fhir_patient_everything:
kind: cloud-healthcare-fhir-patient-everything
description: Use this tool to retrieve resources related to a given patient from a FHIR store
source: healthcare-source
fhir_fetch_page:
kind: cloud-healthcare-fhir-fetch-page
description: Use this tool to fetche a page of FHIR resources from a FHIR store
source: healthcare-source
get_dicom_store:
kind: cloud-healthcare-get-dicom-store
description: Use this tool to get details about a DICOM store in the healthcare dataset
source: healthcare-source
get_dicom_store_metrics:
kind: cloud-healthcare-get-dicom-store-metrics
description: Use this tool to get metrics about a DICOM store in the healthcare dataset
source: healthcare-source
search_dicom_studies:
kind: cloud-healthcare-search-dicom-studies
description: Use this tool to search for DICOM studies in a DICOM store
source: healthcare-source
search_dicom_series:
kind: cloud-healthcare-search-dicom-series
description: Use this tool to search for DICOM series in a DICOM store
source: healthcare-source
search_dicom_instances:
kind: cloud-healthcare-search-dicom-instances
description: Use this tool to search for DICOM instances in a DICOM store
source: healthcare-source
retrieve_rendered_dicom_instance:
kind: cloud-healthcare-retrieve-rendered-dicom-instance
description: Use this tool to retrieve a base64 encoding of a rendered DICOM instance from a DICOM store in JPEG format
source: healthcare-source
toolsets:
cloud_healthcare_dataset_tools:
- get_dataset
- list_dicom_stores
- list_fhir_stores
cloud_healthcare_fhir_tools:
- get_fhir_store
- get_fhir_store_metrics
- get_fhir_resource
- fhir_patient_search
- fhir_patient_everything
- fhir_fetch_page
cloud_healthcare_dicom_tools:
- get_dicom_store
- get_dicom_store_metrics
- search_dicom_studies
- search_dicom_series
- search_dicom_instances
- retrieve_rendered_dicom_instance

View File

@@ -0,0 +1,263 @@
// 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 cloudhealthcare
import (
"context"
"fmt"
"net/http"
"github.com/goccy/go-yaml"
"github.com/googleapis/genai-toolbox/internal/sources"
"github.com/googleapis/genai-toolbox/internal/util"
"go.opentelemetry.io/otel/trace"
"golang.org/x/oauth2"
"golang.org/x/oauth2/google"
"google.golang.org/api/googleapi"
"google.golang.org/api/healthcare/v1"
"google.golang.org/api/option"
)
const SourceKind string = "cloud-healthcare"
// validate interface
var _ sources.SourceConfig = Config{}
type HealthcareServiceCreator func(tokenString string) (*healthcare.Service, error)
func init() {
if !sources.Register(SourceKind, newConfig) {
panic(fmt.Sprintf("source kind %q already registered", SourceKind))
}
}
func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (sources.SourceConfig, error) {
actual := Config{Name: name}
if err := decoder.DecodeContext(ctx, &actual); err != nil {
return nil, err
}
return actual, nil
}
type Config struct {
// Healthcare configs
Name string `yaml:"name" validate:"required"`
Kind string `yaml:"kind" validate:"required"`
Project string `yaml:"project" validate:"required"`
Region string `yaml:"region" validate:"required"`
Dataset string `yaml:"dataset" validate:"required"`
AllowedFHIRStores []string `yaml:"allowedFhirStores"`
AllowedDICOMStores []string `yaml:"allowedDicomStores"`
UseClientOAuth bool `yaml:"useClientOAuth"`
}
func (c Config) SourceConfigKind() string {
return SourceKind
}
func (c Config) Initialize(ctx context.Context, tracer trace.Tracer) (sources.Source, error) {
var service *healthcare.Service
var serviceCreator HealthcareServiceCreator
var tokenSource oauth2.TokenSource
svc, tok, err := initHealthcareConnection(ctx, tracer, c.Name)
if err != nil {
return nil, fmt.Errorf("error creating service from ADC: %w", err)
}
if c.UseClientOAuth {
serviceCreator, err = newHealthcareServiceCreator(ctx, tracer, c.Name)
if err != nil {
return nil, fmt.Errorf("error constructing service creator: %w", err)
}
} else {
service = svc
tokenSource = tok
}
dsName := fmt.Sprintf("projects/%s/locations/%s/datasets/%s", c.Project, c.Region, c.Dataset)
if _, err = svc.Projects.Locations.Datasets.FhirStores.Get(dsName).Do(); err != nil {
if gerr, ok := err.(*googleapi.Error); ok && gerr.Code == http.StatusNotFound {
return nil, fmt.Errorf("dataset '%s' not found", dsName)
}
return nil, fmt.Errorf("failed to verify existence of dataset '%s': %w", dsName, err)
}
allowedFHIRStores := make(map[string]struct{})
for _, store := range c.AllowedFHIRStores {
name := fmt.Sprintf("%s/fhirStores/%s", dsName, store)
_, err := svc.Projects.Locations.Datasets.FhirStores.Get(name).Do()
if err != nil {
if gerr, ok := err.(*googleapi.Error); ok && gerr.Code == http.StatusNotFound {
return nil, fmt.Errorf("allowedFhirStore '%s' not found in dataset '%s'", store, dsName)
}
return nil, fmt.Errorf("failed to verify allowedFhirStore '%s' in datasest '%s': %w", store, dsName, err)
}
allowedFHIRStores[store] = struct{}{}
}
allowedDICOMStores := make(map[string]struct{})
for _, store := range c.AllowedDICOMStores {
name := fmt.Sprintf("%s/dicomStores/%s", dsName, store)
_, err := svc.Projects.Locations.Datasets.DicomStores.Get(name).Do()
if err != nil {
if gerr, ok := err.(*googleapi.Error); ok && gerr.Code == http.StatusNotFound {
return nil, fmt.Errorf("allowedDicomStore '%s' not found in dataset '%s'", store, dsName)
}
return nil, fmt.Errorf("failed to verify allowedDicomFhirStore '%s' in datasest '%s': %w", store, dsName, err)
}
allowedDICOMStores[store] = struct{}{}
}
s := &Source{
name: c.Name,
kind: SourceKind,
project: c.Project,
region: c.Region,
dataset: c.Dataset,
service: service,
serviceCreator: serviceCreator,
tokenSource: tokenSource,
allowedFHIRStores: allowedFHIRStores,
allowedDICOMStores: allowedDICOMStores,
useClientOAuth: c.UseClientOAuth,
}
return s, nil
}
func newHealthcareServiceCreator(ctx context.Context, tracer trace.Tracer, name string) (func(string) (*healthcare.Service, error), error) {
userAgent, err := util.UserAgentFromContext(ctx)
if err != nil {
return nil, err
}
return func(tokenString string) (*healthcare.Service, error) {
return initHealthcareConnectionWithOAuthToken(ctx, tracer, name, userAgent, tokenString)
}, nil
}
func initHealthcareConnectionWithOAuthToken(ctx context.Context, tracer trace.Tracer, name string, userAgent string, tokenString string) (*healthcare.Service, error) {
ctx, span := sources.InitConnectionSpan(ctx, tracer, SourceKind, name)
defer span.End()
// Construct token source
token := &oauth2.Token{
AccessToken: string(tokenString),
}
ts := oauth2.StaticTokenSource(token)
// Initialize the Healthcare service with tokenSource
service, err := healthcare.NewService(ctx, option.WithUserAgent(userAgent), option.WithTokenSource(ts))
if err != nil {
return nil, fmt.Errorf("failed to create Healthcare service: %w", err)
}
service.UserAgent = userAgent
return service, nil
}
func initHealthcareConnection(ctx context.Context, tracer trace.Tracer, name string) (*healthcare.Service, oauth2.TokenSource, error) {
ctx, span := sources.InitConnectionSpan(ctx, tracer, SourceKind, name)
defer span.End()
cred, err := google.FindDefaultCredentials(ctx, healthcare.CloudHealthcareScope)
if err != nil {
return nil, nil, fmt.Errorf("failed to find default Google Cloud credentials with scope %q: %w", healthcare.CloudHealthcareScope, err)
}
userAgent, err := util.UserAgentFromContext(ctx)
if err != nil {
return nil, nil, err
}
service, err := healthcare.NewService(ctx, option.WithUserAgent(userAgent), option.WithCredentials(cred))
if err != nil {
return nil, nil, fmt.Errorf("failed to create Healthcare service: %w", err)
}
service.UserAgent = userAgent
return service, cred.TokenSource, nil
}
var _ sources.Source = &Source{}
type Source struct {
name string `yaml:"name"`
kind string `yaml:"kind"`
project string
region string
dataset string
service *healthcare.Service
serviceCreator HealthcareServiceCreator
tokenSource oauth2.TokenSource
allowedFHIRStores map[string]struct{}
allowedDICOMStores map[string]struct{}
useClientOAuth bool
}
func (s *Source) SourceKind() string {
return SourceKind
}
func (s *Source) Project() string {
return s.project
}
func (s *Source) Region() string {
return s.region
}
func (s *Source) DatasetID() string {
return s.dataset
}
func (s *Source) Service() *healthcare.Service {
return s.service
}
func (s *Source) ServiceCreator() HealthcareServiceCreator {
return s.serviceCreator
}
func (s *Source) TokenSource() oauth2.TokenSource {
return s.tokenSource
}
func (s *Source) AllowedFHIRStores() map[string]struct{} {
if len(s.allowedFHIRStores) == 0 {
return nil
}
return s.allowedFHIRStores
}
func (s *Source) AllowedDICOMStores() map[string]struct{} {
if len(s.allowedDICOMStores) == 0 {
return nil
}
return s.allowedDICOMStores
}
func (s *Source) IsFHIRStoreAllowed(storeID string) bool {
if len(s.allowedFHIRStores) == 0 {
return true
}
_, ok := s.allowedFHIRStores[storeID]
return ok
}
func (s *Source) IsDICOMStoreAllowed(storeID string) bool {
if len(s.allowedDICOMStores) == 0 {
return true
}
_, ok := s.allowedDICOMStores[storeID]
return ok
}
func (s *Source) UseClientAuthorization() bool {
return s.useClientOAuth
}

View File

@@ -0,0 +1,168 @@
// 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 cloudhealthcare_test
import (
"testing"
"github.com/goccy/go-yaml"
"github.com/google/go-cmp/cmp"
"github.com/googleapis/genai-toolbox/internal/server"
"github.com/googleapis/genai-toolbox/internal/sources/cloudhealthcare"
"github.com/googleapis/genai-toolbox/internal/testutils"
)
func TestParseFromYamlCloudHealthcare(t *testing.T) {
tcs := []struct {
desc string
in string
want server.SourceConfigs
}{
{
desc: "basic example",
in: `
sources:
my-instance:
kind: cloud-healthcare
project: my-project
region: us-central1
dataset: my-dataset
`,
want: server.SourceConfigs{
"my-instance": cloudhealthcare.Config{
Name: "my-instance",
Kind: cloudhealthcare.SourceKind,
Project: "my-project",
Region: "us-central1",
Dataset: "my-dataset",
UseClientOAuth: false,
},
},
},
{
desc: "use client auth example",
in: `
sources:
my-instance:
kind: cloud-healthcare
project: my-project
region: us
dataset: my-dataset
useClientOAuth: true
`,
want: server.SourceConfigs{
"my-instance": cloudhealthcare.Config{
Name: "my-instance",
Kind: cloudhealthcare.SourceKind,
Project: "my-project",
Region: "us",
Dataset: "my-dataset",
UseClientOAuth: true,
},
},
},
{
desc: "with allowed stores example",
in: `
sources:
my-instance:
kind: cloud-healthcare
project: my-project
region: us
dataset: my-dataset
allowedFhirStores:
- my-fhir-store
allowedDicomStores:
- my-dicom-store1
- my-dicom-store2
`,
want: server.SourceConfigs{
"my-instance": cloudhealthcare.Config{
Name: "my-instance",
Kind: cloudhealthcare.SourceKind,
Project: "my-project",
Region: "us",
Dataset: "my-dataset",
AllowedFHIRStores: []string{"my-fhir-store"},
AllowedDICOMStores: []string{"my-dicom-store1", "my-dicom-store2"},
},
},
},
}
for _, tc := range tcs {
t.Run(tc.desc, func(t *testing.T) {
got := struct {
Sources server.SourceConfigs `yaml:"sources"`
}{}
// Parse contents
err := yaml.Unmarshal(testutils.FormatYaml(tc.in), &got)
if err != nil {
t.Fatalf("unable to unmarshal: %s", err)
}
if !cmp.Equal(tc.want, got.Sources) {
t.Fatalf("incorrect parse: want %v, got %v", tc.want, got.Sources)
}
})
}
}
func TestFailParseFromYaml(t *testing.T) {
tcs := []struct {
desc string
in string
err string
}{
{
desc: "extra field",
in: `
sources:
my-instance:
kind: cloud-healthcare
project: my-project
region: us-central1
dataset: my-dataset
foo: bar
`,
err: "unable to parse source \"my-instance\" as \"cloud-healthcare\": [2:1] unknown field \"foo\"\n 1 | dataset: my-dataset\n> 2 | foo: bar\n ^\n 3 | kind: cloud-healthcare\n 4 | project: my-project\n 5 | region: us-central1",
},
{
desc: "missing required field",
in: `
sources:
my-instance:
kind: cloud-healthcare
project: my-project
region: us-central1
`,
err: `unable to parse source "my-instance" as "cloud-healthcare": Key: 'Config.Dataset' Error:Field validation for 'Dataset' failed on the 'required' tag`,
},
}
for _, tc := range tcs {
t.Run(tc.desc, func(t *testing.T) {
got := struct {
Sources server.SourceConfigs `yaml:"sources"`
}{}
// Parse contents
err := yaml.Unmarshal(testutils.FormatYaml(tc.in), &got)
if err == nil {
t.Fatalf("expect parsing to fail")
}
errStr := err.Error()
if errStr != tc.err {
t.Fatalf("unexpected error: got %q, want %q", errStr, tc.err)
}
})
}
}

View File

@@ -0,0 +1,206 @@
// 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 fhirfetchpage
import (
"context"
"encoding/json"
"fmt"
"io"
"github.com/goccy/go-yaml"
"github.com/googleapis/genai-toolbox/internal/sources"
healthcareds "github.com/googleapis/genai-toolbox/internal/sources/cloudhealthcare"
"github.com/googleapis/genai-toolbox/internal/tools"
"google.golang.org/api/healthcare/v1"
"net/http"
"golang.org/x/oauth2"
"golang.org/x/oauth2/google"
)
const kind string = "cloud-healthcare-fhir-fetch-page"
const (
pageURLKey = "pageURL"
)
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 compatibleSource interface {
Project() string
Region() string
DatasetID() string
AllowedFHIRStores() map[string]struct{}
Service() *healthcare.Service
ServiceCreator() healthcareds.HealthcareServiceCreator
UseClientAuthorization() bool
}
// validate compatible sources are still compatible
var _ compatibleSource = &healthcareds.Source{}
var compatibleSources = [...]string{healthcareds.SourceKind}
type Config struct {
Name string `yaml:"name" validate:"required"`
Kind string `yaml:"kind" validate:"required"`
Source string `yaml:"source" validate:"required"`
Description string `yaml:"description" validate:"required"`
AuthRequired []string `yaml:"authRequired"`
}
// 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.(compatibleSource)
if !ok {
return nil, fmt.Errorf("invalid source for %q tool: source kind must be one of %q", kind, compatibleSources)
}
urlParameter := tools.NewStringParameter(pageURLKey, "The full URL of the FHIR page to fetch. This would be the value of `Bundle.entry.link.url` field within the response returned from FHIR search or FHIR patient everything operations.")
parameters := tools.Parameters{urlParameter}
mcpManifest := tools.GetMcpManifest(cfg.Name, cfg.Description, cfg.AuthRequired, parameters)
// finish tool setup
t := Tool{
Name: cfg.Name,
Kind: kind,
Parameters: parameters,
AuthRequired: cfg.AuthRequired,
Project: s.Project(),
Region: s.Region(),
Dataset: s.DatasetID(),
AllowedStores: s.AllowedFHIRStores(),
UseClientOAuth: s.UseClientAuthorization(),
ServiceCreator: s.ServiceCreator(),
Service: s.Service(),
manifest: tools.Manifest{Description: cfg.Description, Parameters: parameters.Manifest(), AuthRequired: cfg.AuthRequired},
mcpManifest: mcpManifest,
}
return t, nil
}
// validate interface
var _ tools.Tool = Tool{}
type Tool struct {
Name string `yaml:"name"`
Kind string `yaml:"kind"`
AuthRequired []string `yaml:"authRequired"`
UseClientOAuth bool `yaml:"useClientOAuth"`
Parameters tools.Parameters `yaml:"parameters"`
Project, Region, Dataset string
AllowedStores map[string]struct{}
Service *healthcare.Service
ServiceCreator healthcareds.HealthcareServiceCreator
manifest tools.Manifest
mcpManifest tools.McpManifest
}
func (t Tool) Invoke(ctx context.Context, params tools.ParamValues, accessToken tools.AccessToken) (any, error) {
url, ok := params.AsMap()[pageURLKey].(string)
if !ok {
return nil, fmt.Errorf("invalid or missing '%s' parameter; expected a string", pageURLKey)
}
var httpClient *http.Client
if t.UseClientOAuth {
tokenStr, err := accessToken.ParseBearerToken()
if err != nil {
return nil, fmt.Errorf("error parsing access token: %w", err)
}
ts := oauth2.StaticTokenSource(&oauth2.Token{AccessToken: tokenStr})
httpClient = oauth2.NewClient(ctx, ts)
} else {
// The t.Service object holds a client with the default credentials.
// However, the client is not exported, so we have to create a new one.
var err error
httpClient, err = google.DefaultClient(ctx, healthcare.CloudHealthcareScope)
if err != nil {
return nil, fmt.Errorf("failed to create default http client: %w", err)
}
}
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
if err != nil {
return nil, fmt.Errorf("failed to create http request: %w", err)
}
req.Header.Set("Accept", "application/fhir+json;charset=utf-8")
resp, err := httpClient.Do(req)
if err != nil {
return nil, fmt.Errorf("failed to get fhir page from %q: %w", url, err)
}
defer resp.Body.Close()
respBytes, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("could not read response: %w", err)
}
if resp.StatusCode > 299 {
return nil, fmt.Errorf("read: status %d %s: %s", resp.StatusCode, resp.Status, respBytes)
}
var jsonMap map[string]interface{}
if err := json.Unmarshal([]byte(string(respBytes)), &jsonMap); err != nil {
return nil, fmt.Errorf("could not unmarshal response as json: %w", err)
}
return jsonMap, nil
}
func (t Tool) ParseParams(data map[string]any, claims map[string]map[string]any) (tools.ParamValues, error) {
return tools.ParseParams(t.Parameters, 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)
}
func (t Tool) RequiresClientAuthorization() bool {
return t.UseClientOAuth
}

View File

@@ -0,0 +1,72 @@
// 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 fhirfetchpage_test
import (
"testing"
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"
fhirfetchpage "github.com/googleapis/genai-toolbox/internal/tools/cloudhealthcare/cloudhealthcarefhirfetchpage"
)
func TestParseFromYamlHealthcareFHIRFetchPage(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: cloud-healthcare-fhir-fetch-page
source: my-instance
description: some description
`,
want: server.ToolConfigs{
"example_tool": fhirfetchpage.Config{
Name: "example_tool",
Kind: "cloud-healthcare-fhir-fetch-page",
Source: "my-instance",
Description: "some description",
AuthRequired: []string{},
},
},
},
}
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)
}
})
}
}

View File

@@ -0,0 +1,229 @@
// 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 fhirpatienteverything
import (
"context"
"encoding/json"
"fmt"
"io"
"strings"
"github.com/goccy/go-yaml"
"github.com/googleapis/genai-toolbox/internal/sources"
healthcareds "github.com/googleapis/genai-toolbox/internal/sources/cloudhealthcare"
"github.com/googleapis/genai-toolbox/internal/tools"
"github.com/googleapis/genai-toolbox/internal/tools/cloudhealthcare/common"
"google.golang.org/api/googleapi"
"google.golang.org/api/healthcare/v1"
)
const kind string = "cloud-healthcare-fhir-patient-everything"
const (
patientIDKey = "patientID"
typeFilterKey = "resourceTypesFilter"
sinceFilterKey = "sinceFilter"
)
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 compatibleSource interface {
Project() string
Region() string
DatasetID() string
AllowedFHIRStores() map[string]struct{}
Service() *healthcare.Service
ServiceCreator() healthcareds.HealthcareServiceCreator
UseClientAuthorization() bool
}
// validate compatible sources are still compatible
var _ compatibleSource = &healthcareds.Source{}
var compatibleSources = [...]string{healthcareds.SourceKind}
type Config struct {
Name string `yaml:"name" validate:"required"`
Kind string `yaml:"kind" validate:"required"`
Source string `yaml:"source" validate:"required"`
Description string `yaml:"description" validate:"required"`
AuthRequired []string `yaml:"authRequired"`
}
// 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.(compatibleSource)
if !ok {
return nil, fmt.Errorf("invalid source for %q tool: source kind must be one of %q", kind, compatibleSources)
}
idParameter := tools.NewStringParameter(patientIDKey, "The ID of the patient FHIR resource for which the information is required")
typeFilterParameter := tools.NewArrayParameterWithDefault(typeFilterKey, []any{}, "List of FHIR resource types. If provided, only resources of the specified resource type(s) are returned.", tools.NewStringParameter("resourceType", "A FHIR resource type"))
sinceFilterParameter := tools.NewStringParameterWithDefault(sinceFilterKey, "", "If provided, only resources updated after this time are returned. The time uses the format YYYY-MM-DDThh:mm:ss.sss+zz:zz. The time must be specified to the second and include a time zone. For example, 2015-02-07T13:28:17.239+02:00 or 2017-01-01T00:00:00Z")
parameters := tools.Parameters{idParameter, typeFilterParameter, sinceFilterParameter}
if len(s.AllowedFHIRStores()) != 1 {
parameters = append(parameters, tools.NewStringParameter(common.StoreKey, "The FHIR store ID to retrieve the resource from."))
}
mcpManifest := tools.GetMcpManifest(cfg.Name, cfg.Description, cfg.AuthRequired, parameters)
// finish tool setup
t := Tool{
Name: cfg.Name,
Kind: kind,
Parameters: parameters,
AuthRequired: cfg.AuthRequired,
Project: s.Project(),
Region: s.Region(),
Dataset: s.DatasetID(),
AllowedStores: s.AllowedFHIRStores(),
UseClientOAuth: s.UseClientAuthorization(),
ServiceCreator: s.ServiceCreator(),
Service: s.Service(),
manifest: tools.Manifest{Description: cfg.Description, Parameters: parameters.Manifest(), AuthRequired: cfg.AuthRequired},
mcpManifest: mcpManifest,
}
return t, nil
}
// validate interface
var _ tools.Tool = Tool{}
type Tool struct {
Name string `yaml:"name"`
Kind string `yaml:"kind"`
AuthRequired []string `yaml:"authRequired"`
UseClientOAuth bool `yaml:"useClientOAuth"`
Parameters tools.Parameters `yaml:"parameters"`
Project, Region, Dataset string
AllowedStores map[string]struct{}
Service *healthcare.Service
ServiceCreator healthcareds.HealthcareServiceCreator
manifest tools.Manifest
mcpManifest tools.McpManifest
}
func (t Tool) Invoke(ctx context.Context, params tools.ParamValues, accessToken tools.AccessToken) (any, error) {
storeID, err := common.ValidateAndFetchStoreID(params, t.AllowedStores)
if err != nil {
return nil, err
}
patientID, ok := params.AsMap()[patientIDKey].(string)
if !ok {
return nil, fmt.Errorf("invalid or missing '%s' parameter; expected a string", patientIDKey)
}
svc := t.Service
// Initialize new service if using user OAuth token
if t.UseClientOAuth {
tokenStr, err := accessToken.ParseBearerToken()
if err != nil {
return nil, fmt.Errorf("error parsing access token: %w", err)
}
svc, err = t.ServiceCreator(tokenStr)
if err != nil {
return nil, fmt.Errorf("error creating service from OAuth access token: %w", err)
}
}
name := fmt.Sprintf("projects/%s/locations/%s/datasets/%s/fhirStores/%s/fhir/Patient/%s", t.Project, t.Region, t.Dataset, storeID, patientID)
var opts []googleapi.CallOption
if val, ok := params.AsMap()[typeFilterKey]; ok {
types, ok := val.([]any)
if !ok {
return nil, fmt.Errorf("invalid '%s' parameter; expected a string array", typeFilterKey)
}
typeFilterSlice, err := tools.ConvertAnySliceToTyped(types, "string")
if err != nil {
return nil, fmt.Errorf("can't convert '%s' to array of strings: %s", typeFilterKey, err)
}
if len(typeFilterSlice.([]string)) != 0 {
opts = append(opts, googleapi.QueryParameter("_type", strings.Join(typeFilterSlice.([]string), ",")))
}
}
if since, ok := params.AsMap()[sinceFilterKey]; ok {
sinceStr, ok := since.(string)
if !ok {
return nil, fmt.Errorf("invalid '%s' parameter; expected a string", sinceFilterKey)
}
if sinceStr != "" {
opts = append(opts, googleapi.QueryParameter("_since", sinceStr))
}
}
resp, err := svc.Projects.Locations.Datasets.FhirStores.Fhir.PatientEverything(name).Do(opts...)
if err != nil {
return nil, fmt.Errorf("failed to call patient everything for %q: %w", name, err)
}
defer resp.Body.Close()
respBytes, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("could not read response: %w", err)
}
if resp.StatusCode > 299 {
return nil, fmt.Errorf("patient-everything: status %d %s: %s", resp.StatusCode, resp.Status, respBytes)
}
var jsonMap map[string]interface{}
if err := json.Unmarshal([]byte(string(respBytes)), &jsonMap); err != nil {
return nil, fmt.Errorf("could not unmarshal response as json: %w", err)
}
return jsonMap, nil
}
func (t Tool) ParseParams(data map[string]any, claims map[string]map[string]any) (tools.ParamValues, error) {
return tools.ParseParams(t.Parameters, 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)
}
func (t Tool) RequiresClientAuthorization() bool {
return t.UseClientOAuth
}

View File

@@ -0,0 +1,72 @@
// 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 fhirpatienteverything_test
import (
"testing"
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"
fhirpatienteverything "github.com/googleapis/genai-toolbox/internal/tools/cloudhealthcare/cloudhealthcarefhirpatienteverything"
)
func TestParseFromYamlHealthcareFHIRPatientEverything(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: cloud-healthcare-fhir-patient-everything
source: my-instance
description: some description
`,
want: server.ToolConfigs{
"example_tool": fhirpatienteverything.Config{
Name: "example_tool",
Kind: "cloud-healthcare-fhir-patient-everything",
Source: "my-instance",
Description: "some description",
AuthRequired: []string{},
},
},
},
}
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)
}
})
}
}

View File

@@ -0,0 +1,302 @@
// 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 fhirpatientsearch
import (
"context"
"encoding/json"
"fmt"
"io"
"strings"
"github.com/goccy/go-yaml"
"github.com/googleapis/genai-toolbox/internal/sources"
healthcareds "github.com/googleapis/genai-toolbox/internal/sources/cloudhealthcare"
"github.com/googleapis/genai-toolbox/internal/tools"
"github.com/googleapis/genai-toolbox/internal/tools/cloudhealthcare/common"
"google.golang.org/api/googleapi"
"google.golang.org/api/healthcare/v1"
)
const kind string = "cloud-healthcare-fhir-patient-search"
const (
activeKey = "active"
cityKey = "city"
countryKey = "country"
postalCodeKey = "postalcode"
stateKey = "state"
addressSubstringKey = "addressSubstring"
birthDateRangeKey = "birthDateRange"
deathDateRangeKey = "deathDateRange"
deceasedKey = "deceased"
emailKey = "email"
genderKey = "gender"
addressUseKey = "addressUse"
nameKey = "name"
givenNameKey = "givenName"
familyNameKey = "familyName"
phoneKey = "phone"
languageKey = "language"
identifierKey = "identifier"
summaryKey = "summary"
)
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 compatibleSource interface {
Project() string
Region() string
DatasetID() string
AllowedFHIRStores() map[string]struct{}
Service() *healthcare.Service
ServiceCreator() healthcareds.HealthcareServiceCreator
UseClientAuthorization() bool
}
// validate compatible sources are still compatible
var _ compatibleSource = &healthcareds.Source{}
var compatibleSources = [...]string{healthcareds.SourceKind}
type Config struct {
Name string `yaml:"name" validate:"required"`
Kind string `yaml:"kind" validate:"required"`
Source string `yaml:"source" validate:"required"`
Description string `yaml:"description" validate:"required"`
AuthRequired []string `yaml:"authRequired"`
}
// 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.(compatibleSource)
if !ok {
return nil, fmt.Errorf("invalid source for %q tool: source kind must be one of %q", kind, compatibleSources)
}
parameters := tools.Parameters{
tools.NewStringParameterWithDefault(activeKey, "", "Whether the patient record is active. Use true or false"),
tools.NewStringParameterWithDefault(cityKey, "", "The city of the patient's address"),
tools.NewStringParameterWithDefault(countryKey, "", "The country of the patient's address"),
tools.NewStringParameterWithDefault(postalCodeKey, "", "The postal code of the patient's address"),
tools.NewStringParameterWithDefault(stateKey, "", "The state of the patient's address"),
tools.NewStringParameterWithDefault(addressSubstringKey, "", "A substring to search for in any address field"),
tools.NewStringParameterWithDefault(birthDateRangeKey, "", "A date range for the patient's birthdate in the format YYYY-MM-DD/YYYY-MM-DD. Omit the first or second date to indicate open-ended ranges (e.g. '/2000-01-01' or '1950-01-01/')"),
tools.NewStringParameterWithDefault(deathDateRangeKey, "", "A date range for the patient's death date in the format YYYY-MM-DD/YYYY-MM-DD. Omit the first or second date to indicate open-ended ranges (e.g. '/2000-01-01' or '1950-01-01/')"),
tools.NewStringParameterWithDefault(deceasedKey, "", "Whether the patient is deceased. Use true or false"),
tools.NewStringParameterWithDefault(emailKey, "", "The patient's email address"),
tools.NewStringParameterWithDefault(genderKey, "", "The patient's gender. Must be one of 'male', 'female', 'other', or 'unknown'"),
tools.NewStringParameterWithDefault(addressUseKey, "", "The use of the patient's address. Must be one of 'home', 'work', 'temp', 'old', or 'billing'"),
tools.NewStringParameterWithDefault(nameKey, "", "The patient's name. Can be a family name, given name, or both"),
tools.NewStringParameterWithDefault(givenNameKey, "", "A portion of the given name of the patient"),
tools.NewStringParameterWithDefault(familyNameKey, "", "A portion of the family name of the patient"),
tools.NewStringParameterWithDefault(phoneKey, "", "The patient's phone number"),
tools.NewStringParameterWithDefault(languageKey, "", "The patient's preferred language. Must be a valid BCP-47 code (e.g. 'en-US', 'es')"),
tools.NewStringParameterWithDefault(identifierKey, "", "An identifier for the patient"),
tools.NewBooleanParameterWithDefault(summaryKey, true, "Requests the server to return a subset of the resource. Return a limited subset of elements from the resource. Enabled by default to reduce response size. Use get-fhir-resource tool to get full resource details (preferred) or set to false to disable."),
}
if len(s.AllowedFHIRStores()) != 1 {
parameters = append(parameters, tools.NewStringParameter(common.StoreKey, "The FHIR store ID to retrieve the resource from."))
}
mcpManifest := tools.GetMcpManifest(cfg.Name, cfg.Description, cfg.AuthRequired, parameters)
// finish tool setup
t := Tool{
Name: cfg.Name,
Kind: kind,
Parameters: parameters,
AuthRequired: cfg.AuthRequired,
Project: s.Project(),
Region: s.Region(),
Dataset: s.DatasetID(),
AllowedStores: s.AllowedFHIRStores(),
UseClientOAuth: s.UseClientAuthorization(),
ServiceCreator: s.ServiceCreator(),
Service: s.Service(),
manifest: tools.Manifest{Description: cfg.Description, Parameters: parameters.Manifest(), AuthRequired: cfg.AuthRequired},
mcpManifest: mcpManifest,
}
return t, nil
}
// validate interface
var _ tools.Tool = Tool{}
type Tool struct {
Name string `yaml:"name"`
Kind string `yaml:"kind"`
AuthRequired []string `yaml:"authRequired"`
UseClientOAuth bool `yaml:"useClientOAuth"`
Parameters tools.Parameters `yaml:"parameters"`
Project, Region, Dataset string
AllowedStores map[string]struct{}
Service *healthcare.Service
ServiceCreator healthcareds.HealthcareServiceCreator
manifest tools.Manifest
mcpManifest tools.McpManifest
}
func (t Tool) Invoke(ctx context.Context, params tools.ParamValues, accessToken tools.AccessToken) (any, error) {
storeID, err := common.ValidateAndFetchStoreID(params, t.AllowedStores)
if err != nil {
return nil, err
}
svc := t.Service
// Initialize new service if using user OAuth token
if t.UseClientOAuth {
tokenStr, err := accessToken.ParseBearerToken()
if err != nil {
return nil, fmt.Errorf("error parsing access token: %w", err)
}
svc, err = t.ServiceCreator(tokenStr)
if err != nil {
return nil, fmt.Errorf("error creating service from OAuth access token: %w", err)
}
}
var summary bool
var opts []googleapi.CallOption
for k, v := range params.AsMap() {
if k == common.StoreKey {
continue
}
if k == summaryKey {
var ok bool
summary, ok = v.(bool)
if !ok {
return nil, fmt.Errorf("invalid '%s' parameter; expected a boolean", summaryKey)
}
continue
}
val, ok := v.(string)
if !ok {
return nil, fmt.Errorf("invalid parameter '%s'; expected a string", k)
}
if val == "" {
continue
}
switch k {
case activeKey, deceasedKey, emailKey, genderKey, phoneKey, languageKey, identifierKey:
opts = append(opts, googleapi.QueryParameter(k, val))
case cityKey, countryKey, postalCodeKey, stateKey:
opts = append(opts, googleapi.QueryParameter("address-"+k, val))
case addressSubstringKey:
opts = append(opts, googleapi.QueryParameter("address", val))
case birthDateRangeKey, deathDateRangeKey:
key := "birthdate"
if k == deathDateRangeKey {
key = "death-date"
}
parts := strings.Split(val, "/")
if len(parts) != 2 {
return nil, fmt.Errorf("invalid '%s' format; expected YYYY-MM-DD/YYYY-MM-DD", k)
}
var values []string
if parts[0] != "" {
values = append(values, "ge"+parts[0])
}
if parts[1] != "" {
values = append(values, "le"+parts[1])
}
if len(values) != 0 {
opts = append(opts, googleapi.QueryParameter(key, values...))
}
case addressUseKey:
opts = append(opts, googleapi.QueryParameter("address-use", val))
case nameKey:
parts := strings.Split(val, " ")
for _, part := range parts {
opts = append(opts, googleapi.QueryParameter("name", part))
}
case givenNameKey:
opts = append(opts, googleapi.QueryParameter("given", val))
case familyNameKey:
opts = append(opts, googleapi.QueryParameter("family", val))
default:
return nil, fmt.Errorf("unexpected parameter key %q", k)
}
}
if summary {
opts = append(opts, googleapi.QueryParameter("_summary", "text"))
}
name := fmt.Sprintf("projects/%s/locations/%s/datasets/%s/fhirStores/%s", t.Project, t.Region, t.Dataset, storeID)
resp, err := svc.Projects.Locations.Datasets.FhirStores.Fhir.SearchType(name, "Patient", &healthcare.SearchResourcesRequest{ResourceType: "Patient"}).Do(opts...)
if err != nil {
return nil, fmt.Errorf("failed to search patient resources: %w", err)
}
defer resp.Body.Close()
respBytes, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("could not read response: %w", err)
}
if resp.StatusCode > 299 {
return nil, fmt.Errorf("search: status %d %s: %s", resp.StatusCode, resp.Status, respBytes)
}
var jsonMap map[string]interface{}
if err := json.Unmarshal([]byte(string(respBytes)), &jsonMap); err != nil {
return nil, fmt.Errorf("could not unmarshal response as json: %w", err)
}
return jsonMap, nil
}
func (t Tool) ParseParams(data map[string]any, claims map[string]map[string]any) (tools.ParamValues, error) {
return tools.ParseParams(t.Parameters, 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)
}
func (t Tool) RequiresClientAuthorization() bool {
return t.UseClientOAuth
}

View File

@@ -0,0 +1,72 @@
// 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 fhirpatientsearch_test
import (
"testing"
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"
fhirpatientsearch "github.com/googleapis/genai-toolbox/internal/tools/cloudhealthcare/cloudhealthcarefhirpatientsearch"
)
func TestParseFromYamlHealthcareFHIRPatientSearch(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: cloud-healthcare-fhir-patient-search
source: my-instance
description: some description
`,
want: server.ToolConfigs{
"example_tool": fhirpatientsearch.Config{
Name: "example_tool",
Kind: "cloud-healthcare-fhir-patient-search",
Source: "my-instance",
Description: "some description",
AuthRequired: []string{},
},
},
},
}
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)
}
})
}
}

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 gethealthcaredataset
import (
"context"
"fmt"
"github.com/goccy/go-yaml"
"github.com/googleapis/genai-toolbox/internal/sources"
healthcareds "github.com/googleapis/genai-toolbox/internal/sources/cloudhealthcare"
"github.com/googleapis/genai-toolbox/internal/tools"
"google.golang.org/api/healthcare/v1"
)
const kind string = "cloud-healthcare-get-dataset"
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 compatibleSource interface {
Project() string
Region() string
DatasetID() string
Service() *healthcare.Service
ServiceCreator() healthcareds.HealthcareServiceCreator
UseClientAuthorization() bool
}
// validate compatible sources are still compatible
var _ compatibleSource = &healthcareds.Source{}
var compatibleSources = [...]string{healthcareds.SourceKind}
type Config struct {
Name string `yaml:"name" validate:"required"`
Kind string `yaml:"kind" validate:"required"`
Source string `yaml:"source" validate:"required"`
Description string `yaml:"description" validate:"required"`
AuthRequired []string `yaml:"authRequired"`
}
// 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.(compatibleSource)
if !ok {
return nil, fmt.Errorf("invalid source for %q tool: source kind must be one of %q", kind, compatibleSources)
}
parameters := tools.Parameters{}
mcpManifest := tools.GetMcpManifest(cfg.Name, cfg.Description, cfg.AuthRequired, parameters)
// finish tool setup
t := Tool{
Name: cfg.Name,
Kind: kind,
Parameters: parameters,
AuthRequired: cfg.AuthRequired,
Project: s.Project(),
Region: s.Region(),
Dataset: s.DatasetID(),
UseClientOAuth: s.UseClientAuthorization(),
ServiceCreator: s.ServiceCreator(),
Service: s.Service(),
manifest: tools.Manifest{Description: cfg.Description, Parameters: parameters.Manifest(), AuthRequired: cfg.AuthRequired},
mcpManifest: mcpManifest,
}
return t, nil
}
// validate interface
var _ tools.Tool = Tool{}
type Tool struct {
Name string `yaml:"name"`
Kind string `yaml:"kind"`
AuthRequired []string `yaml:"authRequired"`
UseClientOAuth bool `yaml:"useClientOAuth"`
Parameters tools.Parameters `yaml:"parameters"`
Project, Region, Dataset string
Service *healthcare.Service
ServiceCreator healthcareds.HealthcareServiceCreator
manifest tools.Manifest
mcpManifest tools.McpManifest
}
func (t Tool) Invoke(ctx context.Context, params tools.ParamValues, accessToken tools.AccessToken) (any, error) {
svc := t.Service
var err error
// Initialize new service if using user OAuth token
if t.UseClientOAuth {
tokenStr, err := accessToken.ParseBearerToken()
if err != nil {
return nil, fmt.Errorf("error parsing access token: %w", err)
}
svc, err = t.ServiceCreator(tokenStr)
if err != nil {
return nil, fmt.Errorf("error creating service from OAuth access token: %w", err)
}
}
datasetName := fmt.Sprintf("projects/%s/locations/%s/datasets/%s", t.Project, t.Region, t.Dataset)
dataset, err := svc.Projects.Locations.Datasets.Get(datasetName).Do()
if err != nil {
return nil, fmt.Errorf("failed to get dataset %q: %w", datasetName, err)
}
return dataset, nil
}
func (t Tool) ParseParams(data map[string]any, claims map[string]map[string]any) (tools.ParamValues, error) {
return tools.ParseParams(t.Parameters, 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)
}
func (t Tool) RequiresClientAuthorization() bool {
return t.UseClientOAuth
}

View File

@@ -0,0 +1,72 @@
// 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 gethealthcaredataset_test
import (
"testing"
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"
getdataset "github.com/googleapis/genai-toolbox/internal/tools/cloudhealthcare/cloudhealthcaregetdataset"
)
func TestParseFromYamlGetHealthcareDataset(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: cloud-healthcare-get-dataset
source: my-instance
description: some description
`,
want: server.ToolConfigs{
"example_tool": getdataset.Config{
Name: "example_tool",
Kind: "cloud-healthcare-get-dataset",
Source: "my-instance",
Description: "some description",
AuthRequired: []string{},
},
},
},
}
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)
}
})
}
}

View File

@@ -0,0 +1,176 @@
// 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 getdicomstore
import (
"context"
"fmt"
"github.com/goccy/go-yaml"
"github.com/googleapis/genai-toolbox/internal/sources"
healthcareds "github.com/googleapis/genai-toolbox/internal/sources/cloudhealthcare"
"github.com/googleapis/genai-toolbox/internal/tools"
"github.com/googleapis/genai-toolbox/internal/tools/cloudhealthcare/common"
"google.golang.org/api/healthcare/v1"
)
const kind string = "cloud-healthcare-get-dicom-store"
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 compatibleSource interface {
Project() string
Region() string
DatasetID() string
AllowedDICOMStores() map[string]struct{}
Service() *healthcare.Service
ServiceCreator() healthcareds.HealthcareServiceCreator
UseClientAuthorization() bool
}
// validate compatible sources are still compatible
var _ compatibleSource = &healthcareds.Source{}
var compatibleSources = [...]string{healthcareds.SourceKind}
type Config struct {
Name string `yaml:"name" validate:"required"`
Kind string `yaml:"kind" validate:"required"`
Source string `yaml:"source" validate:"required"`
Description string `yaml:"description" validate:"required"`
AuthRequired []string `yaml:"authRequired"`
}
// 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.(compatibleSource)
if !ok {
return nil, fmt.Errorf("invalid source for %q tool: source kind must be one of %q", kind, compatibleSources)
}
parameters := tools.Parameters{}
if len(s.AllowedDICOMStores()) != 1 {
parameters = append(parameters, tools.NewStringParameter(common.StoreKey, "The DICOM store ID to get details for."))
}
mcpManifest := tools.GetMcpManifest(cfg.Name, cfg.Description, cfg.AuthRequired, parameters)
// finish tool setup
t := Tool{
Name: cfg.Name,
Kind: kind,
Parameters: parameters,
AuthRequired: cfg.AuthRequired,
Project: s.Project(),
Region: s.Region(),
Dataset: s.DatasetID(),
AllowedStores: s.AllowedDICOMStores(),
UseClientOAuth: s.UseClientAuthorization(),
ServiceCreator: s.ServiceCreator(),
Service: s.Service(),
manifest: tools.Manifest{Description: cfg.Description, Parameters: parameters.Manifest(), AuthRequired: cfg.AuthRequired},
mcpManifest: mcpManifest,
}
return t, nil
}
// validate interface
var _ tools.Tool = Tool{}
type Tool struct {
Name string `yaml:"name"`
Kind string `yaml:"kind"`
AuthRequired []string `yaml:"authRequired"`
UseClientOAuth bool `yaml:"useClientOAuth"`
Parameters tools.Parameters `yaml:"parameters"`
Project, Region, Dataset string
AllowedStores map[string]struct{}
Service *healthcare.Service
ServiceCreator healthcareds.HealthcareServiceCreator
manifest tools.Manifest
mcpManifest tools.McpManifest
}
func (t Tool) Invoke(ctx context.Context, params tools.ParamValues, accessToken tools.AccessToken) (any, error) {
storeID, err := common.ValidateAndFetchStoreID(params, t.AllowedStores)
if err != nil {
return nil, err
}
svc := t.Service
// Initialize new service if using user OAuth token
if t.UseClientOAuth {
tokenStr, err := accessToken.ParseBearerToken()
if err != nil {
return nil, fmt.Errorf("error parsing access token: %w", err)
}
svc, err = t.ServiceCreator(tokenStr)
if err != nil {
return nil, fmt.Errorf("error creating service from OAuth access token: %w", err)
}
}
storeName := fmt.Sprintf("projects/%s/locations/%s/datasets/%s/dicomStores/%s", t.Project, t.Region, t.Dataset, storeID)
store, err := svc.Projects.Locations.Datasets.DicomStores.Get(storeName).Do()
if err != nil {
return nil, fmt.Errorf("failed to get DICOM store %q: %w", storeName, err)
}
return store, nil
}
func (t Tool) ParseParams(data map[string]any, claims map[string]map[string]any) (tools.ParamValues, error) {
return tools.ParseParams(t.Parameters, 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)
}
func (t Tool) RequiresClientAuthorization() bool {
return t.UseClientOAuth
}

View File

@@ -0,0 +1,72 @@
// 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 getdicomstore_test
import (
"testing"
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"
getdicomstore "github.com/googleapis/genai-toolbox/internal/tools/cloudhealthcare/cloudhealthcaregetdicomstore"
)
func TestParseFromYamlHealthcareGetDICOMStore(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: cloud-healthcare-get-dicom-store
source: my-instance
description: some description
`,
want: server.ToolConfigs{
"example_tool": getdicomstore.Config{
Name: "example_tool",
Kind: "cloud-healthcare-get-dicom-store",
Source: "my-instance",
Description: "some description",
AuthRequired: []string{},
},
},
},
}
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)
}
})
}
}

View File

@@ -0,0 +1,176 @@
// 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 getdicomstoremetrics
import (
"context"
"fmt"
"github.com/goccy/go-yaml"
"github.com/googleapis/genai-toolbox/internal/sources"
healthcareds "github.com/googleapis/genai-toolbox/internal/sources/cloudhealthcare"
"github.com/googleapis/genai-toolbox/internal/tools"
"github.com/googleapis/genai-toolbox/internal/tools/cloudhealthcare/common"
"google.golang.org/api/healthcare/v1"
)
const kind string = "cloud-healthcare-get-dicom-store-metrics"
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 compatibleSource interface {
Project() string
Region() string
DatasetID() string
AllowedDICOMStores() map[string]struct{}
Service() *healthcare.Service
ServiceCreator() healthcareds.HealthcareServiceCreator
UseClientAuthorization() bool
}
// validate compatible sources are still compatible
var _ compatibleSource = &healthcareds.Source{}
var compatibleSources = [...]string{healthcareds.SourceKind}
type Config struct {
Name string `yaml:"name" validate:"required"`
Kind string `yaml:"kind" validate:"required"`
Source string `yaml:"source" validate:"required"`
Description string `yaml:"description" validate:"required"`
AuthRequired []string `yaml:"authRequired"`
}
// 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.(compatibleSource)
if !ok {
return nil, fmt.Errorf("invalid source for %q tool: source kind must be one of %q", kind, compatibleSources)
}
parameters := tools.Parameters{}
if len(s.AllowedDICOMStores()) != 1 {
parameters = append(parameters, tools.NewStringParameter(common.StoreKey, "The DICOM store ID to get metrics for."))
}
mcpManifest := tools.GetMcpManifest(cfg.Name, cfg.Description, cfg.AuthRequired, parameters)
// finish tool setup
t := Tool{
Name: cfg.Name,
Kind: kind,
Parameters: parameters,
AuthRequired: cfg.AuthRequired,
Project: s.Project(),
Region: s.Region(),
Dataset: s.DatasetID(),
AllowedStores: s.AllowedDICOMStores(),
UseClientOAuth: s.UseClientAuthorization(),
ServiceCreator: s.ServiceCreator(),
Service: s.Service(),
manifest: tools.Manifest{Description: cfg.Description, Parameters: parameters.Manifest(), AuthRequired: cfg.AuthRequired},
mcpManifest: mcpManifest,
}
return t, nil
}
// validate interface
var _ tools.Tool = Tool{}
type Tool struct {
Name string `yaml:"name"`
Kind string `yaml:"kind"`
AuthRequired []string `yaml:"authRequired"`
UseClientOAuth bool `yaml:"useClientOAuth"`
Parameters tools.Parameters `yaml:"parameters"`
Project, Region, Dataset string
AllowedStores map[string]struct{}
Service *healthcare.Service
ServiceCreator healthcareds.HealthcareServiceCreator
manifest tools.Manifest
mcpManifest tools.McpManifest
}
func (t Tool) Invoke(ctx context.Context, params tools.ParamValues, accessToken tools.AccessToken) (any, error) {
storeID, err := common.ValidateAndFetchStoreID(params, t.AllowedStores)
if err != nil {
return nil, err
}
svc := t.Service
// Initialize new service if using user OAuth token
if t.UseClientOAuth {
tokenStr, err := accessToken.ParseBearerToken()
if err != nil {
return nil, fmt.Errorf("error parsing access token: %w", err)
}
svc, err = t.ServiceCreator(tokenStr)
if err != nil {
return nil, fmt.Errorf("error creating service from OAuth access token: %w", err)
}
}
storeName := fmt.Sprintf("projects/%s/locations/%s/datasets/%s/dicomStores/%s", t.Project, t.Region, t.Dataset, storeID)
store, err := svc.Projects.Locations.Datasets.DicomStores.GetDICOMStoreMetrics(storeName).Do()
if err != nil {
return nil, fmt.Errorf("failed to get metrics for DICOM store %q: %w", storeName, err)
}
return store, nil
}
func (t Tool) ParseParams(data map[string]any, claims map[string]map[string]any) (tools.ParamValues, error) {
return tools.ParseParams(t.Parameters, 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)
}
func (t Tool) RequiresClientAuthorization() bool {
return t.UseClientOAuth
}

View File

@@ -0,0 +1,72 @@
// 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 getdicomstoremetrics_test
import (
"testing"
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"
getdicomstoremetrics "github.com/googleapis/genai-toolbox/internal/tools/cloudhealthcare/cloudhealthcaregetdicomstoremetrics"
)
func TestParseFromYamlHealthcareGetDICOMStoreMetrics(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: cloud-healthcare-get-dicom-store-metrics
source: my-instance
description: some description
`,
want: server.ToolConfigs{
"example_tool": getdicomstoremetrics.Config{
Name: "example_tool",
Kind: "cloud-healthcare-get-dicom-store-metrics",
Source: "my-instance",
Description: "some description",
AuthRequired: []string{},
},
},
},
}
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)
}
})
}
}

View File

@@ -0,0 +1,208 @@
// 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 getfhirresource
import (
"context"
"encoding/json"
"fmt"
"io"
"github.com/goccy/go-yaml"
"github.com/googleapis/genai-toolbox/internal/sources"
healthcareds "github.com/googleapis/genai-toolbox/internal/sources/cloudhealthcare"
"github.com/googleapis/genai-toolbox/internal/tools"
"github.com/googleapis/genai-toolbox/internal/tools/cloudhealthcare/common"
"google.golang.org/api/healthcare/v1"
)
const kind string = "cloud-healthcare-get-fhir-resource"
const (
typeKey = "resourceType"
idKey = "resourceID"
)
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 compatibleSource interface {
Project() string
Region() string
DatasetID() string
AllowedFHIRStores() map[string]struct{}
Service() *healthcare.Service
ServiceCreator() healthcareds.HealthcareServiceCreator
UseClientAuthorization() bool
}
// validate compatible sources are still compatible
var _ compatibleSource = &healthcareds.Source{}
var compatibleSources = [...]string{healthcareds.SourceKind}
type Config struct {
Name string `yaml:"name" validate:"required"`
Kind string `yaml:"kind" validate:"required"`
Source string `yaml:"source" validate:"required"`
Description string `yaml:"description" validate:"required"`
AuthRequired []string `yaml:"authRequired"`
}
// 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.(compatibleSource)
if !ok {
return nil, fmt.Errorf("invalid source for %q tool: source kind must be one of %q", kind, compatibleSources)
}
typeParameter := tools.NewStringParameter(typeKey, "The FHIR resource type to retrieve (e.g., Patient, Observation).")
idParameter := tools.NewStringParameter(idKey, "The ID of the FHIR resource to retrieve.")
parameters := tools.Parameters{typeParameter, idParameter}
if len(s.AllowedFHIRStores()) != 1 {
parameters = append(parameters, tools.NewStringParameter(common.StoreKey, "The FHIR store ID to retrieve the resource from."))
}
mcpManifest := tools.GetMcpManifest(cfg.Name, cfg.Description, cfg.AuthRequired, parameters)
// finish tool setup
t := Tool{
Name: cfg.Name,
Kind: kind,
Parameters: parameters,
AuthRequired: cfg.AuthRequired,
Project: s.Project(),
Region: s.Region(),
Dataset: s.DatasetID(),
AllowedStores: s.AllowedFHIRStores(),
UseClientOAuth: s.UseClientAuthorization(),
ServiceCreator: s.ServiceCreator(),
Service: s.Service(),
manifest: tools.Manifest{Description: cfg.Description, Parameters: parameters.Manifest(), AuthRequired: cfg.AuthRequired},
mcpManifest: mcpManifest,
}
return t, nil
}
// validate interface
var _ tools.Tool = Tool{}
type Tool struct {
Name string `yaml:"name"`
Kind string `yaml:"kind"`
AuthRequired []string `yaml:"authRequired"`
UseClientOAuth bool `yaml:"useClientOAuth"`
Parameters tools.Parameters `yaml:"parameters"`
Project, Region, Dataset string
AllowedStores map[string]struct{}
Service *healthcare.Service
ServiceCreator healthcareds.HealthcareServiceCreator
manifest tools.Manifest
mcpManifest tools.McpManifest
}
func (t Tool) Invoke(ctx context.Context, params tools.ParamValues, accessToken tools.AccessToken) (any, error) {
storeID, err := common.ValidateAndFetchStoreID(params, t.AllowedStores)
if err != nil {
return nil, err
}
resType, ok := params.AsMap()[typeKey].(string)
if !ok {
return nil, fmt.Errorf("invalid or missing '%s' parameter; expected a string", typeKey)
}
resID, ok := params.AsMap()[idKey].(string)
if !ok {
return nil, fmt.Errorf("invalid or missing '%s' parameter; expected a string", idKey)
}
svc := t.Service
// Initialize new service if using user OAuth token
if t.UseClientOAuth {
tokenStr, err := accessToken.ParseBearerToken()
if err != nil {
return nil, fmt.Errorf("error parsing access token: %w", err)
}
svc, err = t.ServiceCreator(tokenStr)
if err != nil {
return nil, fmt.Errorf("error creating service from OAuth access token: %w", err)
}
}
name := fmt.Sprintf("projects/%s/locations/%s/datasets/%s/fhirStores/%s/fhir/%s/%s", t.Project, t.Region, t.Dataset, storeID, resType, resID)
call := svc.Projects.Locations.Datasets.FhirStores.Fhir.Read(name)
call.Header().Set("Content-Type", "application/fhir+json;charset=utf-8")
resp, err := call.Do()
if err != nil {
return nil, fmt.Errorf("failed to get fhir resource %q: %w", name, err)
}
defer resp.Body.Close()
respBytes, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("could not read response: %w", err)
}
if resp.StatusCode > 299 {
return nil, fmt.Errorf("read: status %d %s: %s", resp.StatusCode, resp.Status, respBytes)
}
var jsonMap map[string]interface{}
if err := json.Unmarshal([]byte(string(respBytes)), &jsonMap); err != nil {
return nil, fmt.Errorf("could not unmarshal response as json: %w", err)
}
return jsonMap, nil
}
func (t Tool) ParseParams(data map[string]any, claims map[string]map[string]any) (tools.ParamValues, error) {
return tools.ParseParams(t.Parameters, 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)
}
func (t Tool) RequiresClientAuthorization() bool {
return t.UseClientOAuth
}

View File

@@ -0,0 +1,72 @@
// 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 getfhirresource_test
import (
"testing"
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"
getfhirresource "github.com/googleapis/genai-toolbox/internal/tools/cloudhealthcare/cloudhealthcaregetfhirresource"
)
func TestParseFromYamlHealthcareGetFHIRResource(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: cloud-healthcare-get-fhir-resource
source: my-instance
description: some description
`,
want: server.ToolConfigs{
"example_tool": getfhirresource.Config{
Name: "example_tool",
Kind: "cloud-healthcare-get-fhir-resource",
Source: "my-instance",
Description: "some description",
AuthRequired: []string{},
},
},
},
}
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)
}
})
}
}

View File

@@ -0,0 +1,176 @@
// 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 getfhirstore
import (
"context"
"fmt"
"github.com/goccy/go-yaml"
"github.com/googleapis/genai-toolbox/internal/sources"
healthcareds "github.com/googleapis/genai-toolbox/internal/sources/cloudhealthcare"
"github.com/googleapis/genai-toolbox/internal/tools"
"github.com/googleapis/genai-toolbox/internal/tools/cloudhealthcare/common"
"google.golang.org/api/healthcare/v1"
)
const kind string = "cloud-healthcare-get-fhir-store"
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 compatibleSource interface {
Project() string
Region() string
DatasetID() string
AllowedFHIRStores() map[string]struct{}
Service() *healthcare.Service
ServiceCreator() healthcareds.HealthcareServiceCreator
UseClientAuthorization() bool
}
// validate compatible sources are still compatible
var _ compatibleSource = &healthcareds.Source{}
var compatibleSources = [...]string{healthcareds.SourceKind}
type Config struct {
Name string `yaml:"name" validate:"required"`
Kind string `yaml:"kind" validate:"required"`
Source string `yaml:"source" validate:"required"`
Description string `yaml:"description" validate:"required"`
AuthRequired []string `yaml:"authRequired"`
}
// 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.(compatibleSource)
if !ok {
return nil, fmt.Errorf("invalid source for %q tool: source kind must be one of %q", kind, compatibleSources)
}
parameters := tools.Parameters{}
if len(s.AllowedFHIRStores()) != 1 {
parameters = append(parameters, tools.NewStringParameter(common.StoreKey, "The FHIR store ID to get details for."))
}
mcpManifest := tools.GetMcpManifest(cfg.Name, cfg.Description, cfg.AuthRequired, parameters)
// finish tool setup
t := Tool{
Name: cfg.Name,
Kind: kind,
Parameters: parameters,
AuthRequired: cfg.AuthRequired,
Project: s.Project(),
Region: s.Region(),
Dataset: s.DatasetID(),
AllowedStores: s.AllowedFHIRStores(),
UseClientOAuth: s.UseClientAuthorization(),
ServiceCreator: s.ServiceCreator(),
Service: s.Service(),
manifest: tools.Manifest{Description: cfg.Description, Parameters: parameters.Manifest(), AuthRequired: cfg.AuthRequired},
mcpManifest: mcpManifest,
}
return t, nil
}
// validate interface
var _ tools.Tool = Tool{}
type Tool struct {
Name string `yaml:"name"`
Kind string `yaml:"kind"`
AuthRequired []string `yaml:"authRequired"`
UseClientOAuth bool `yaml:"useClientOAuth"`
Parameters tools.Parameters `yaml:"parameters"`
Project, Region, Dataset string
AllowedStores map[string]struct{}
Service *healthcare.Service
ServiceCreator healthcareds.HealthcareServiceCreator
manifest tools.Manifest
mcpManifest tools.McpManifest
}
func (t Tool) Invoke(ctx context.Context, params tools.ParamValues, accessToken tools.AccessToken) (any, error) {
storeID, err := common.ValidateAndFetchStoreID(params, t.AllowedStores)
if err != nil {
return nil, err
}
svc := t.Service
// Initialize new service if using user OAuth token
if t.UseClientOAuth {
tokenStr, err := accessToken.ParseBearerToken()
if err != nil {
return nil, fmt.Errorf("error parsing access token: %w", err)
}
svc, err = t.ServiceCreator(tokenStr)
if err != nil {
return nil, fmt.Errorf("error creating service from OAuth access token: %w", err)
}
}
storeName := fmt.Sprintf("projects/%s/locations/%s/datasets/%s/fhirStores/%s", t.Project, t.Region, t.Dataset, storeID)
store, err := svc.Projects.Locations.Datasets.FhirStores.Get(storeName).Do()
if err != nil {
return nil, fmt.Errorf("failed to get FHIR store %q: %w", storeName, err)
}
return store, nil
}
func (t Tool) ParseParams(data map[string]any, claims map[string]map[string]any) (tools.ParamValues, error) {
return tools.ParseParams(t.Parameters, 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)
}
func (t Tool) RequiresClientAuthorization() bool {
return t.UseClientOAuth
}

View File

@@ -0,0 +1,72 @@
// 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 getfhirstore_test
import (
"testing"
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"
getfhirstore "github.com/googleapis/genai-toolbox/internal/tools/cloudhealthcare/cloudhealthcaregetfhirstore"
)
func TestParseFromYamlHealthcareGetFHIRStore(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: cloud-healthcare-get-fhir-store
source: my-instance
description: some description
`,
want: server.ToolConfigs{
"example_tool": getfhirstore.Config{
Name: "example_tool",
Kind: "cloud-healthcare-get-fhir-store",
Source: "my-instance",
Description: "some description",
AuthRequired: []string{},
},
},
},
}
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)
}
})
}
}

View File

@@ -0,0 +1,176 @@
// 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 getfhirstoremetrics
import (
"context"
"fmt"
"github.com/goccy/go-yaml"
"github.com/googleapis/genai-toolbox/internal/sources"
healthcareds "github.com/googleapis/genai-toolbox/internal/sources/cloudhealthcare"
"github.com/googleapis/genai-toolbox/internal/tools"
"github.com/googleapis/genai-toolbox/internal/tools/cloudhealthcare/common"
"google.golang.org/api/healthcare/v1"
)
const kind string = "cloud-healthcare-get-fhir-store-metrics"
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 compatibleSource interface {
Project() string
Region() string
DatasetID() string
AllowedFHIRStores() map[string]struct{}
Service() *healthcare.Service
ServiceCreator() healthcareds.HealthcareServiceCreator
UseClientAuthorization() bool
}
// validate compatible sources are still compatible
var _ compatibleSource = &healthcareds.Source{}
var compatibleSources = [...]string{healthcareds.SourceKind}
type Config struct {
Name string `yaml:"name" validate:"required"`
Kind string `yaml:"kind" validate:"required"`
Source string `yaml:"source" validate:"required"`
Description string `yaml:"description" validate:"required"`
AuthRequired []string `yaml:"authRequired"`
}
// 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.(compatibleSource)
if !ok {
return nil, fmt.Errorf("invalid source for %q tool: source kind must be one of %q", kind, compatibleSources)
}
parameters := tools.Parameters{}
if len(s.AllowedFHIRStores()) != 1 {
parameters = append(parameters, tools.NewStringParameter(common.StoreKey, "The FHIR store ID to get metrics for."))
}
mcpManifest := tools.GetMcpManifest(cfg.Name, cfg.Description, cfg.AuthRequired, parameters)
// finish tool setup
t := Tool{
Name: cfg.Name,
Kind: kind,
Parameters: parameters,
AuthRequired: cfg.AuthRequired,
Project: s.Project(),
Region: s.Region(),
Dataset: s.DatasetID(),
AllowedStores: s.AllowedFHIRStores(),
UseClientOAuth: s.UseClientAuthorization(),
ServiceCreator: s.ServiceCreator(),
Service: s.Service(),
manifest: tools.Manifest{Description: cfg.Description, Parameters: parameters.Manifest(), AuthRequired: cfg.AuthRequired},
mcpManifest: mcpManifest,
}
return t, nil
}
// validate interface
var _ tools.Tool = Tool{}
type Tool struct {
Name string `yaml:"name"`
Kind string `yaml:"kind"`
AuthRequired []string `yaml:"authRequired"`
UseClientOAuth bool `yaml:"useClientOAuth"`
Parameters tools.Parameters `yaml:"parameters"`
Project, Region, Dataset string
AllowedStores map[string]struct{}
Service *healthcare.Service
ServiceCreator healthcareds.HealthcareServiceCreator
manifest tools.Manifest
mcpManifest tools.McpManifest
}
func (t Tool) Invoke(ctx context.Context, params tools.ParamValues, accessToken tools.AccessToken) (any, error) {
storeID, err := common.ValidateAndFetchStoreID(params, t.AllowedStores)
if err != nil {
return nil, err
}
svc := t.Service
// Initialize new service if using user OAuth token
if t.UseClientOAuth {
tokenStr, err := accessToken.ParseBearerToken()
if err != nil {
return nil, fmt.Errorf("error parsing access token: %w", err)
}
svc, err = t.ServiceCreator(tokenStr)
if err != nil {
return nil, fmt.Errorf("error creating service from OAuth access token: %w", err)
}
}
storeName := fmt.Sprintf("projects/%s/locations/%s/datasets/%s/fhirStores/%s", t.Project, t.Region, t.Dataset, storeID)
store, err := svc.Projects.Locations.Datasets.FhirStores.GetFHIRStoreMetrics(storeName).Do()
if err != nil {
return nil, fmt.Errorf("failed to get metrics for FHIR store %q: %w", storeName, err)
}
return store, nil
}
func (t Tool) ParseParams(data map[string]any, claims map[string]map[string]any) (tools.ParamValues, error) {
return tools.ParseParams(t.Parameters, 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)
}
func (t Tool) RequiresClientAuthorization() bool {
return t.UseClientOAuth
}

View File

@@ -0,0 +1,72 @@
// 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 getfhirstoremetrics_test
import (
"testing"
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"
getfhirstoremetrics "github.com/googleapis/genai-toolbox/internal/tools/cloudhealthcare/cloudhealthcaregetfhirstoremetrics"
)
func TestParseFromYamlHealthcareGetFHIRStoreMetrics(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: cloud-healthcare-get-fhir-store-metrics
source: my-instance
description: some description
`,
want: server.ToolConfigs{
"example_tool": getfhirstoremetrics.Config{
Name: "example_tool",
Kind: "cloud-healthcare-get-fhir-store-metrics",
Source: "my-instance",
Description: "some description",
AuthRequired: []string{},
},
},
},
}
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)
}
})
}
}

View File

@@ -0,0 +1,184 @@
// 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 listdicomstores
import (
"context"
"fmt"
"strings"
"github.com/goccy/go-yaml"
"github.com/googleapis/genai-toolbox/internal/sources"
healthcareds "github.com/googleapis/genai-toolbox/internal/sources/cloudhealthcare"
"github.com/googleapis/genai-toolbox/internal/tools"
"google.golang.org/api/healthcare/v1"
)
const kind string = "cloud-healthcare-list-dicom-stores"
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 compatibleSource interface {
Project() string
Region() string
DatasetID() string
AllowedDICOMStores() map[string]struct{}
Service() *healthcare.Service
ServiceCreator() healthcareds.HealthcareServiceCreator
UseClientAuthorization() bool
}
// validate compatible sources are still compatible
var _ compatibleSource = &healthcareds.Source{}
var compatibleSources = [...]string{healthcareds.SourceKind}
type Config struct {
Name string `yaml:"name" validate:"required"`
Kind string `yaml:"kind" validate:"required"`
Source string `yaml:"source" validate:"required"`
Description string `yaml:"description" validate:"required"`
AuthRequired []string `yaml:"authRequired"`
}
// 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.(compatibleSource)
if !ok {
return nil, fmt.Errorf("invalid source for %q tool: source kind must be one of %q", kind, compatibleSources)
}
parameters := tools.Parameters{}
mcpManifest := tools.GetMcpManifest(cfg.Name, cfg.Description, cfg.AuthRequired, parameters)
// finish tool setup
t := Tool{
Name: cfg.Name,
Kind: kind,
Parameters: parameters,
AuthRequired: cfg.AuthRequired,
Project: s.Project(),
Region: s.Region(),
Dataset: s.DatasetID(),
AllowedStores: s.AllowedDICOMStores(),
UseClientOAuth: s.UseClientAuthorization(),
ServiceCreator: s.ServiceCreator(),
Service: s.Service(),
manifest: tools.Manifest{Description: cfg.Description, Parameters: parameters.Manifest(), AuthRequired: cfg.AuthRequired},
mcpManifest: mcpManifest,
}
return t, nil
}
// validate interface
var _ tools.Tool = Tool{}
type Tool struct {
Name string `yaml:"name"`
Kind string `yaml:"kind"`
AuthRequired []string `yaml:"authRequired"`
UseClientOAuth bool `yaml:"useClientOAuth"`
Parameters tools.Parameters `yaml:"parameters"`
Project, Region, Dataset string
AllowedStores map[string]struct{}
Service *healthcare.Service
ServiceCreator healthcareds.HealthcareServiceCreator
manifest tools.Manifest
mcpManifest tools.McpManifest
}
func (t Tool) Invoke(ctx context.Context, params tools.ParamValues, accessToken tools.AccessToken) (any, error) {
svc := t.Service
var err error
// Initialize new service if using user OAuth token
if t.UseClientOAuth {
tokenStr, err := accessToken.ParseBearerToken()
if err != nil {
return nil, fmt.Errorf("error parsing access token: %w", err)
}
svc, err = t.ServiceCreator(tokenStr)
if err != nil {
return nil, fmt.Errorf("error creating service from OAuth access token: %w", err)
}
}
datasetName := fmt.Sprintf("projects/%s/locations/%s/datasets/%s", t.Project, t.Region, t.Dataset)
stores, err := svc.Projects.Locations.Datasets.DicomStores.List(datasetName).Do()
if err != nil {
return nil, fmt.Errorf("failed to get dataset %q: %w", datasetName, err)
}
var filtered []*healthcare.DicomStore
for _, store := range stores.DicomStores {
if len(t.AllowedStores) == 0 {
filtered = append(filtered, store)
continue
}
if len(store.Name) == 0 {
continue
}
parts := strings.Split(store.Name, "/")
if _, ok := t.AllowedStores[parts[len(parts)-1]]; ok {
filtered = append(filtered, store)
}
}
return filtered, nil
}
func (t Tool) ParseParams(data map[string]any, claims map[string]map[string]any) (tools.ParamValues, error) {
return tools.ParseParams(t.Parameters, 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)
}
func (t Tool) RequiresClientAuthorization() bool {
return t.UseClientOAuth
}

View File

@@ -0,0 +1,72 @@
// 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 listdicomstores_test
import (
"testing"
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"
listdicomstores "github.com/googleapis/genai-toolbox/internal/tools/cloudhealthcare/cloudhealthcarelistdicomstores"
)
func TestParseFromYamlHealthcareListDICOMStores(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: cloud-healthcare-list-dicom-stores
source: my-instance
description: some description
`,
want: server.ToolConfigs{
"example_tool": listdicomstores.Config{
Name: "example_tool",
Kind: "cloud-healthcare-list-dicom-stores",
Source: "my-instance",
Description: "some description",
AuthRequired: []string{},
},
},
},
}
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)
}
})
}
}

View File

@@ -0,0 +1,184 @@
// 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 listfhirstores
import (
"context"
"fmt"
"strings"
"github.com/goccy/go-yaml"
"github.com/googleapis/genai-toolbox/internal/sources"
healthcareds "github.com/googleapis/genai-toolbox/internal/sources/cloudhealthcare"
"github.com/googleapis/genai-toolbox/internal/tools"
"google.golang.org/api/healthcare/v1"
)
const kind string = "cloud-healthcare-list-fhir-stores"
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 compatibleSource interface {
Project() string
Region() string
DatasetID() string
AllowedFHIRStores() map[string]struct{}
Service() *healthcare.Service
ServiceCreator() healthcareds.HealthcareServiceCreator
UseClientAuthorization() bool
}
// validate compatible sources are still compatible
var _ compatibleSource = &healthcareds.Source{}
var compatibleSources = [...]string{healthcareds.SourceKind}
type Config struct {
Name string `yaml:"name" validate:"required"`
Kind string `yaml:"kind" validate:"required"`
Source string `yaml:"source" validate:"required"`
Description string `yaml:"description" validate:"required"`
AuthRequired []string `yaml:"authRequired"`
}
// 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.(compatibleSource)
if !ok {
return nil, fmt.Errorf("invalid source for %q tool: source kind must be one of %q", kind, compatibleSources)
}
parameters := tools.Parameters{}
mcpManifest := tools.GetMcpManifest(cfg.Name, cfg.Description, cfg.AuthRequired, parameters)
// finish tool setup
t := Tool{
Name: cfg.Name,
Kind: kind,
Parameters: parameters,
AuthRequired: cfg.AuthRequired,
Project: s.Project(),
Region: s.Region(),
Dataset: s.DatasetID(),
AllowedStores: s.AllowedFHIRStores(),
UseClientOAuth: s.UseClientAuthorization(),
ServiceCreator: s.ServiceCreator(),
Service: s.Service(),
manifest: tools.Manifest{Description: cfg.Description, Parameters: parameters.Manifest(), AuthRequired: cfg.AuthRequired},
mcpManifest: mcpManifest,
}
return t, nil
}
// validate interface
var _ tools.Tool = Tool{}
type Tool struct {
Name string `yaml:"name"`
Kind string `yaml:"kind"`
AuthRequired []string `yaml:"authRequired"`
UseClientOAuth bool `yaml:"useClientOAuth"`
Parameters tools.Parameters `yaml:"parameters"`
Project, Region, Dataset string
AllowedStores map[string]struct{}
Service *healthcare.Service
ServiceCreator healthcareds.HealthcareServiceCreator
manifest tools.Manifest
mcpManifest tools.McpManifest
}
func (t Tool) Invoke(ctx context.Context, params tools.ParamValues, accessToken tools.AccessToken) (any, error) {
svc := t.Service
var err error
// Initialize new service if using user OAuth token
if t.UseClientOAuth {
tokenStr, err := accessToken.ParseBearerToken()
if err != nil {
return nil, fmt.Errorf("error parsing access token: %w", err)
}
svc, err = t.ServiceCreator(tokenStr)
if err != nil {
return nil, fmt.Errorf("error creating service from OAuth access token: %w", err)
}
}
datasetName := fmt.Sprintf("projects/%s/locations/%s/datasets/%s", t.Project, t.Region, t.Dataset)
stores, err := svc.Projects.Locations.Datasets.FhirStores.List(datasetName).Do()
if err != nil {
return nil, fmt.Errorf("failed to get dataset %q: %w", datasetName, err)
}
var filtered []*healthcare.FhirStore
for _, store := range stores.FhirStores {
if len(t.AllowedStores) == 0 {
filtered = append(filtered, store)
continue
}
if len(store.Name) == 0 {
continue
}
parts := strings.Split(store.Name, "/")
if _, ok := t.AllowedStores[parts[len(parts)-1]]; ok {
filtered = append(filtered, store)
}
}
return filtered, nil
}
func (t Tool) ParseParams(data map[string]any, claims map[string]map[string]any) (tools.ParamValues, error) {
return tools.ParseParams(t.Parameters, 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)
}
func (t Tool) RequiresClientAuthorization() bool {
return t.UseClientOAuth
}

View File

@@ -0,0 +1,72 @@
// 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 listfhirstores_test
import (
"testing"
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"
listfhirstores "github.com/googleapis/genai-toolbox/internal/tools/cloudhealthcare/cloudhealthcarelistfhirstores"
)
func TestParseFromYamlHealthcareListFHIRStores(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: cloud-healthcare-list-fhir-stores
source: my-instance
description: some description
`,
want: server.ToolConfigs{
"example_tool": listfhirstores.Config{
Name: "example_tool",
Kind: "cloud-healthcare-list-fhir-stores",
Source: "my-instance",
Description: "some description",
AuthRequired: []string{},
},
},
},
}
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)
}
})
}
}

View File

@@ -0,0 +1,218 @@
// 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 retrieverendereddicominstance
import (
"context"
"encoding/base64"
"fmt"
"io"
"github.com/goccy/go-yaml"
"github.com/googleapis/genai-toolbox/internal/sources"
healthcareds "github.com/googleapis/genai-toolbox/internal/sources/cloudhealthcare"
"github.com/googleapis/genai-toolbox/internal/tools"
"github.com/googleapis/genai-toolbox/internal/tools/cloudhealthcare/common"
"google.golang.org/api/healthcare/v1"
)
const kind string = "cloud-healthcare-retrieve-rendered-dicom-instance"
const (
studyInstanceUIDKey = "StudyInstanceUID"
seriesInstanceUIDKey = "SeriesInstanceUID"
sopInstanceUIDKey = "SOPInstanceUID"
frameNumberKey = "FrameNumber"
)
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 compatibleSource interface {
Project() string
Region() string
DatasetID() string
AllowedDICOMStores() map[string]struct{}
Service() *healthcare.Service
ServiceCreator() healthcareds.HealthcareServiceCreator
UseClientAuthorization() bool
}
// validate compatible sources are still compatible
var _ compatibleSource = &healthcareds.Source{}
var compatibleSources = [...]string{healthcareds.SourceKind}
type Config struct {
Name string `yaml:"name" validate:"required"`
Kind string `yaml:"kind" validate:"required"`
Source string `yaml:"source" validate:"required"`
Description string `yaml:"description" validate:"required"`
AuthRequired []string `yaml:"authRequired"`
}
// 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.(compatibleSource)
if !ok {
return nil, fmt.Errorf("invalid source for %q tool: source kind must be one of %q", kind, compatibleSources)
}
parameters := tools.Parameters{
tools.NewStringParameter(studyInstanceUIDKey, "The UID of the DICOM study"),
tools.NewStringParameter(seriesInstanceUIDKey, "The UID of the DICOM series"),
tools.NewStringParameter(sopInstanceUIDKey, "The UID of the SOP instance."),
tools.NewIntParameterWithDefault(frameNumberKey, 1, "The frame number to retrieve (1-based). Only applicable to multi-frame instances."),
}
if len(s.AllowedDICOMStores()) != 1 {
parameters = append(parameters, tools.NewStringParameter(common.StoreKey, "The DICOM store ID to get details for."))
}
mcpManifest := tools.GetMcpManifest(cfg.Name, cfg.Description, cfg.AuthRequired, parameters)
// finish tool setup
t := Tool{
Name: cfg.Name,
Kind: kind,
Parameters: parameters,
AuthRequired: cfg.AuthRequired,
Project: s.Project(),
Region: s.Region(),
Dataset: s.DatasetID(),
AllowedStores: s.AllowedDICOMStores(),
UseClientOAuth: s.UseClientAuthorization(),
ServiceCreator: s.ServiceCreator(),
Service: s.Service(),
manifest: tools.Manifest{Description: cfg.Description, Parameters: parameters.Manifest(), AuthRequired: cfg.AuthRequired},
mcpManifest: mcpManifest,
}
return t, nil
}
// validate interface
var _ tools.Tool = Tool{}
type Tool struct {
Name string `yaml:"name"`
Kind string `yaml:"kind"`
AuthRequired []string `yaml:"authRequired"`
UseClientOAuth bool `yaml:"useClientOAuth"`
Parameters tools.Parameters `yaml:"parameters"`
Project, Region, Dataset string
AllowedStores map[string]struct{}
Service *healthcare.Service
ServiceCreator healthcareds.HealthcareServiceCreator
manifest tools.Manifest
mcpManifest tools.McpManifest
}
func (t Tool) Invoke(ctx context.Context, params tools.ParamValues, accessToken tools.AccessToken) (any, error) {
storeID, err := common.ValidateAndFetchStoreID(params, t.AllowedStores)
if err != nil {
return nil, err
}
svc := t.Service
// Initialize new service if using user OAuth token
if t.UseClientOAuth {
tokenStr, err := accessToken.ParseBearerToken()
if err != nil {
return nil, fmt.Errorf("error parsing access token: %w", err)
}
svc, err = t.ServiceCreator(tokenStr)
if err != nil {
return nil, fmt.Errorf("error creating service from OAuth access token: %w", err)
}
}
study, ok := params.AsMap()[studyInstanceUIDKey].(string)
if !ok {
return nil, fmt.Errorf("invalid '%s' parameter; expected a string", studyInstanceUIDKey)
}
series, ok := params.AsMap()[seriesInstanceUIDKey].(string)
if !ok {
return nil, fmt.Errorf("invalid '%s' parameter; expected a string", seriesInstanceUIDKey)
}
sop, ok := params.AsMap()[sopInstanceUIDKey].(string)
if !ok {
return nil, fmt.Errorf("invalid '%s' parameter; expected a string", sopInstanceUIDKey)
}
frame, ok := params.AsMap()[frameNumberKey].(int)
if !ok {
return nil, fmt.Errorf("invalid '%s' parameter; expected an integer", frameNumberKey)
}
name := fmt.Sprintf("projects/%s/locations/%s/datasets/%s/dicomStores/%s", t.Project, t.Region, t.Dataset, storeID)
dicomWebPath := fmt.Sprintf("studies/%s/series/%s/instances/%s/frames/%d/rendered", study, series, sop, frame)
call := svc.Projects.Locations.Datasets.DicomStores.Studies.Series.Instances.Frames.RetrieveRendered(name, dicomWebPath)
call.Header().Set("Accept", "image/jpeg")
resp, err := call.Do()
if err != nil {
return nil, fmt.Errorf("unable to retrieve dicom instance rendered image: %w", err)
}
defer resp.Body.Close()
respBytes, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("could not read response: %w", err)
}
if resp.StatusCode > 299 {
return nil, fmt.Errorf("RetrieveRendered: status %d %s: %s", resp.StatusCode, resp.Status, respBytes)
}
base64String := base64.StdEncoding.EncodeToString(respBytes)
return base64String, nil
}
func (t Tool) ParseParams(data map[string]any, claims map[string]map[string]any) (tools.ParamValues, error) {
return tools.ParseParams(t.Parameters, 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)
}
func (t Tool) RequiresClientAuthorization() bool {
return t.UseClientOAuth
}

View File

@@ -0,0 +1,72 @@
// 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 retrieverendereddicominstance_test
import (
"testing"
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"
retrieverendereddicominstance "github.com/googleapis/genai-toolbox/internal/tools/cloudhealthcare/cloudhealthcareretrieverendereddicominstance"
)
func TestParseFromYamlHealthcareRetrieveRenderedDICOMInstance(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: cloud-healthcare-retrieve-rendered-dicom-instance
source: my-instance
description: some description
`,
want: server.ToolConfigs{
"example_tool": retrieverendereddicominstance.Config{
Name: "example_tool",
Kind: "cloud-healthcare-retrieve-rendered-dicom-instance",
Source: "my-instance",
Description: "some description",
AuthRequired: []string{},
},
},
},
}
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)
}
})
}
}

View File

@@ -0,0 +1,248 @@
// 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 searchdicominstances
import (
"context"
"encoding/json"
"fmt"
"io"
"strings"
"github.com/goccy/go-yaml"
"github.com/googleapis/genai-toolbox/internal/sources"
healthcareds "github.com/googleapis/genai-toolbox/internal/sources/cloudhealthcare"
"github.com/googleapis/genai-toolbox/internal/tools"
"github.com/googleapis/genai-toolbox/internal/tools/cloudhealthcare/common"
"google.golang.org/api/googleapi"
"google.golang.org/api/healthcare/v1"
)
const kind string = "cloud-healthcare-search-dicom-instances"
const (
studyInstanceUIDKey = "StudyInstanceUID"
patientNameKey = "PatientName"
patientIDKey = "PatientID"
accessionNumberKey = "AccessionNumber"
referringPhysicianNameKey = "ReferringPhysicianName"
studyDateKey = "StudyDate"
seriesInstanceUIDKey = "SeriesInstanceUID"
modalityKey = "Modality"
sopInstanceUIDKey = "SOPInstanceUID"
)
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 compatibleSource interface {
Project() string
Region() string
DatasetID() string
AllowedDICOMStores() map[string]struct{}
Service() *healthcare.Service
ServiceCreator() healthcareds.HealthcareServiceCreator
UseClientAuthorization() bool
}
// validate compatible sources are still compatible
var _ compatibleSource = &healthcareds.Source{}
var compatibleSources = [...]string{healthcareds.SourceKind}
type Config struct {
Name string `yaml:"name" validate:"required"`
Kind string `yaml:"kind" validate:"required"`
Source string `yaml:"source" validate:"required"`
Description string `yaml:"description" validate:"required"`
AuthRequired []string `yaml:"authRequired"`
}
// 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.(compatibleSource)
if !ok {
return nil, fmt.Errorf("invalid source for %q tool: source kind must be one of %q", kind, compatibleSources)
}
parameters := tools.Parameters{
tools.NewStringParameterWithDefault(studyInstanceUIDKey, "", "The UID of the DICOM study"),
tools.NewStringParameterWithDefault(patientNameKey, "", "The name of the patient"),
tools.NewStringParameterWithDefault(patientIDKey, "", "The ID of the patient"),
tools.NewStringParameterWithDefault(accessionNumberKey, "", "The accession number of the series"),
tools.NewStringParameterWithDefault(referringPhysicianNameKey, "", "The name of the referring physician"),
tools.NewStringParameterWithDefault(studyDateKey, "", "The date of the study in the format `YYYYMMDD`. You can also specify a date range in the format `YYYYMMDD-YYYYMMDD`"),
tools.NewStringParameterWithDefault(seriesInstanceUIDKey, "", "The UID of the DICOM series"),
tools.NewStringParameterWithDefault(modalityKey, "", "The modality of the series"),
tools.NewStringParameterWithDefault(sopInstanceUIDKey, "", "The UID of the SOP instance."),
tools.NewBooleanParameterWithDefault(common.EnablePatientNameFuzzyMatchingKey, false, `Whether to enable fuzzy matching for patient names. Fuzzy matching will perform tokenization and normalization of both the value of PatientName in the query and the stored value. It will match if any search token is a prefix of any stored token. For example, if PatientName is "John^Doe", then "jo", "Do" and "John Doe" will all match. However "ohn" will not match`),
tools.NewArrayParameterWithDefault(common.IncludeAttributesKey, []any{}, "List of attributeIDs, such as DICOM tag IDs or keywords. Set to [\"all\"] to return all available tags.", tools.NewStringParameter("attributeID", "The attributeID to include. Set to 'all' to return all available tags")),
}
if len(s.AllowedDICOMStores()) != 1 {
parameters = append(parameters, tools.NewStringParameter(common.StoreKey, "The DICOM store ID to get details for."))
}
mcpManifest := tools.GetMcpManifest(cfg.Name, cfg.Description, cfg.AuthRequired, parameters)
// finish tool setup
t := Tool{
Name: cfg.Name,
Kind: kind,
Parameters: parameters,
AuthRequired: cfg.AuthRequired,
Project: s.Project(),
Region: s.Region(),
Dataset: s.DatasetID(),
AllowedStores: s.AllowedDICOMStores(),
UseClientOAuth: s.UseClientAuthorization(),
ServiceCreator: s.ServiceCreator(),
Service: s.Service(),
manifest: tools.Manifest{Description: cfg.Description, Parameters: parameters.Manifest(), AuthRequired: cfg.AuthRequired},
mcpManifest: mcpManifest,
}
return t, nil
}
// validate interface
var _ tools.Tool = Tool{}
type Tool struct {
Name string `yaml:"name"`
Kind string `yaml:"kind"`
AuthRequired []string `yaml:"authRequired"`
UseClientOAuth bool `yaml:"useClientOAuth"`
Parameters tools.Parameters `yaml:"parameters"`
Project, Region, Dataset string
AllowedStores map[string]struct{}
Service *healthcare.Service
ServiceCreator healthcareds.HealthcareServiceCreator
manifest tools.Manifest
mcpManifest tools.McpManifest
}
func (t Tool) Invoke(ctx context.Context, params tools.ParamValues, accessToken tools.AccessToken) (any, error) {
storeID, err := common.ValidateAndFetchStoreID(params, t.AllowedStores)
if err != nil {
return nil, err
}
svc := t.Service
// Initialize new service if using user OAuth token
if t.UseClientOAuth {
tokenStr, err := accessToken.ParseBearerToken()
if err != nil {
return nil, fmt.Errorf("error parsing access token: %w", err)
}
svc, err = t.ServiceCreator(tokenStr)
if err != nil {
return nil, fmt.Errorf("error creating service from OAuth access token: %w", err)
}
}
opts, err := common.ParseDICOMSearchParameters(params, []string{sopInstanceUIDKey, patientNameKey, patientIDKey, accessionNumberKey, referringPhysicianNameKey, studyDateKey, modalityKey})
if err != nil {
return nil, err
}
paramsMap := params.AsMap()
dicomWebPath := "instances"
if studyInstanceUID, ok := paramsMap[studyInstanceUIDKey]; ok {
id, ok := studyInstanceUID.(string)
if !ok {
return nil, fmt.Errorf("invalid '%s' parameter; expected a string", studyInstanceUIDKey)
}
if id != "" {
dicomWebPath = fmt.Sprintf("studies/%s/instances", id)
}
}
if seriesInstanceUID, ok := paramsMap[seriesInstanceUIDKey]; ok {
id, ok := seriesInstanceUID.(string)
if !ok {
return nil, fmt.Errorf("invalid '%s' parameter; expected a string", seriesInstanceUIDKey)
}
if id != "" {
if dicomWebPath != "instances" {
dicomWebPath = fmt.Sprintf("%s/series/%s/instances", strings.TrimSuffix(dicomWebPath, "/instances"), id)
} else {
opts = append(opts, googleapi.QueryParameter(seriesInstanceUIDKey, id))
}
}
}
name := fmt.Sprintf("projects/%s/locations/%s/datasets/%s/dicomStores/%s", t.Project, t.Region, t.Dataset, storeID)
resp, err := svc.Projects.Locations.Datasets.DicomStores.SearchForInstances(name, dicomWebPath).Do(opts...)
if err != nil {
return nil, fmt.Errorf("failed to search dicom instances: %w", err)
}
defer resp.Body.Close()
respBytes, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("could not read response: %w", err)
}
if resp.StatusCode > 299 {
return nil, fmt.Errorf("search: status %d %s: %s", resp.StatusCode, resp.Status, respBytes)
}
if len(respBytes) == 0 {
return []interface{}{}, nil
}
var result []interface{}
if err := json.Unmarshal([]byte(string(respBytes)), &result); err != nil {
return nil, fmt.Errorf("could not unmarshal response as list: %w", err)
}
return result, nil
}
func (t Tool) ParseParams(data map[string]any, claims map[string]map[string]any) (tools.ParamValues, error) {
return tools.ParseParams(t.Parameters, 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)
}
func (t Tool) RequiresClientAuthorization() bool {
return t.UseClientOAuth
}

View File

@@ -0,0 +1,72 @@
// 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 searchdicominstances_test
import (
"testing"
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"
searchdicominstances "github.com/googleapis/genai-toolbox/internal/tools/cloudhealthcare/cloudhealthcaresearchdicominstances"
)
func TestParseFromYamlHealthcareSearchDICOMInstances(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: cloud-healthcare-search-dicom-instances
source: my-instance
description: some description
`,
want: server.ToolConfigs{
"example_tool": searchdicominstances.Config{
Name: "example_tool",
Kind: "cloud-healthcare-search-dicom-instances",
Source: "my-instance",
Description: "some description",
AuthRequired: []string{},
},
},
},
}
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)
}
})
}
}

View File

@@ -0,0 +1,231 @@
// 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 searchdicomseries
import (
"context"
"encoding/json"
"fmt"
"io"
"github.com/goccy/go-yaml"
"github.com/googleapis/genai-toolbox/internal/sources"
healthcareds "github.com/googleapis/genai-toolbox/internal/sources/cloudhealthcare"
"github.com/googleapis/genai-toolbox/internal/tools"
"github.com/googleapis/genai-toolbox/internal/tools/cloudhealthcare/common"
"google.golang.org/api/healthcare/v1"
)
const kind string = "cloud-healthcare-search-dicom-series"
const (
studyInstanceUIDKey = "StudyInstanceUID"
patientNameKey = "PatientName"
patientIDKey = "PatientID"
accessionNumberKey = "AccessionNumber"
referringPhysicianNameKey = "ReferringPhysicianName"
studyDateKey = "StudyDate"
seriesInstanceUIDKey = "SeriesInstanceUID"
modalityKey = "Modality"
)
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 compatibleSource interface {
Project() string
Region() string
DatasetID() string
AllowedDICOMStores() map[string]struct{}
Service() *healthcare.Service
ServiceCreator() healthcareds.HealthcareServiceCreator
UseClientAuthorization() bool
}
// validate compatible sources are still compatible
var _ compatibleSource = &healthcareds.Source{}
var compatibleSources = [...]string{healthcareds.SourceKind}
type Config struct {
Name string `yaml:"name" validate:"required"`
Kind string `yaml:"kind" validate:"required"`
Source string `yaml:"source" validate:"required"`
Description string `yaml:"description" validate:"required"`
AuthRequired []string `yaml:"authRequired"`
}
// 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.(compatibleSource)
if !ok {
return nil, fmt.Errorf("invalid source for %q tool: source kind must be one of %q", kind, compatibleSources)
}
parameters := tools.Parameters{
tools.NewStringParameterWithDefault(studyInstanceUIDKey, "", "The UID of the DICOM study"),
tools.NewStringParameterWithDefault(patientNameKey, "", "The name of the patient"),
tools.NewStringParameterWithDefault(patientIDKey, "", "The ID of the patient"),
tools.NewStringParameterWithDefault(accessionNumberKey, "", "The accession number of the series"),
tools.NewStringParameterWithDefault(referringPhysicianNameKey, "", "The name of the referring physician"),
tools.NewStringParameterWithDefault(studyDateKey, "", "The date of the study in the format `YYYYMMDD`. You can also specify a date range in the format `YYYYMMDD-YYYYMMDD`"),
tools.NewStringParameterWithDefault(seriesInstanceUIDKey, "", "The UID of the DICOM series"),
tools.NewStringParameterWithDefault(modalityKey, "", "The modality of the series"),
tools.NewBooleanParameterWithDefault(common.EnablePatientNameFuzzyMatchingKey, false, `Whether to enable fuzzy matching for patient names. Fuzzy matching will perform tokenization and normalization of both the value of PatientName in the query and the stored value. It will match if any search token is a prefix of any stored token. For example, if PatientName is "John^Doe", then "jo", "Do" and "John Doe" will all match. However "ohn" will not match`),
tools.NewArrayParameterWithDefault(common.IncludeAttributesKey, []any{}, "List of attributeIDs, such as DICOM tag IDs or keywords. Set to [\"all\"] to return all available tags.", tools.NewStringParameter("attributeID", "The attributeID to include. Set to 'all' to return all available tags")),
}
if len(s.AllowedDICOMStores()) != 1 {
parameters = append(parameters, tools.NewStringParameter(common.StoreKey, "The DICOM store ID to get details for."))
}
mcpManifest := tools.GetMcpManifest(cfg.Name, cfg.Description, cfg.AuthRequired, parameters)
// finish tool setup
t := Tool{
Name: cfg.Name,
Kind: kind,
Parameters: parameters,
AuthRequired: cfg.AuthRequired,
Project: s.Project(),
Region: s.Region(),
Dataset: s.DatasetID(),
AllowedStores: s.AllowedDICOMStores(),
UseClientOAuth: s.UseClientAuthorization(),
ServiceCreator: s.ServiceCreator(),
Service: s.Service(),
manifest: tools.Manifest{Description: cfg.Description, Parameters: parameters.Manifest(), AuthRequired: cfg.AuthRequired},
mcpManifest: mcpManifest,
}
return t, nil
}
// validate interface
var _ tools.Tool = Tool{}
type Tool struct {
Name string `yaml:"name"`
Kind string `yaml:"kind"`
AuthRequired []string `yaml:"authRequired"`
UseClientOAuth bool `yaml:"useClientOAuth"`
Parameters tools.Parameters `yaml:"parameters"`
Project, Region, Dataset string
AllowedStores map[string]struct{}
Service *healthcare.Service
ServiceCreator healthcareds.HealthcareServiceCreator
manifest tools.Manifest
mcpManifest tools.McpManifest
}
func (t Tool) Invoke(ctx context.Context, params tools.ParamValues, accessToken tools.AccessToken) (any, error) {
storeID, err := common.ValidateAndFetchStoreID(params, t.AllowedStores)
if err != nil {
return nil, err
}
svc := t.Service
// Initialize new service if using user OAuth token
if t.UseClientOAuth {
tokenStr, err := accessToken.ParseBearerToken()
if err != nil {
return nil, fmt.Errorf("error parsing access token: %w", err)
}
svc, err = t.ServiceCreator(tokenStr)
if err != nil {
return nil, fmt.Errorf("error creating service from OAuth access token: %w", err)
}
}
opts, err := common.ParseDICOMSearchParameters(params, []string{seriesInstanceUIDKey, patientNameKey, patientIDKey, accessionNumberKey, referringPhysicianNameKey, studyDateKey, modalityKey})
if err != nil {
return nil, err
}
paramsMap := params.AsMap()
dicomWebPath := "series"
if studyInstanceUID, ok := paramsMap[studyInstanceUIDKey]; ok {
id, ok := studyInstanceUID.(string)
if !ok {
return nil, fmt.Errorf("invalid '%s' parameter; expected a string", studyInstanceUIDKey)
}
if id != "" {
dicomWebPath = fmt.Sprintf("studies/%s/series", id)
}
}
name := fmt.Sprintf("projects/%s/locations/%s/datasets/%s/dicomStores/%s", t.Project, t.Region, t.Dataset, storeID)
resp, err := svc.Projects.Locations.Datasets.DicomStores.SearchForSeries(name, dicomWebPath).Do(opts...)
if err != nil {
return nil, fmt.Errorf("failed to search dicom series: %w", err)
}
defer resp.Body.Close()
respBytes, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("could not read response: %w", err)
}
if resp.StatusCode > 299 {
return nil, fmt.Errorf("search: status %d %s: %s", resp.StatusCode, resp.Status, respBytes)
}
if len(respBytes) == 0 {
return []interface{}{}, nil
}
var result []interface{}
if err := json.Unmarshal([]byte(string(respBytes)), &result); err != nil {
return nil, fmt.Errorf("could not unmarshal response as list: %w", err)
}
return result, nil
}
func (t Tool) ParseParams(data map[string]any, claims map[string]map[string]any) (tools.ParamValues, error) {
return tools.ParseParams(t.Parameters, 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)
}
func (t Tool) RequiresClientAuthorization() bool {
return t.UseClientOAuth
}

View File

@@ -0,0 +1,72 @@
// 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 searchdicomseries_test
import (
"testing"
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"
searchdicomseries "github.com/googleapis/genai-toolbox/internal/tools/cloudhealthcare/cloudhealthcaresearchdicomseries"
)
func TestParseFromYamlHealthcareSearchDICOMSeries(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: cloud-healthcare-search-dicom-series
source: my-instance
description: some description
`,
want: server.ToolConfigs{
"example_tool": searchdicomseries.Config{
Name: "example_tool",
Kind: "cloud-healthcare-search-dicom-series",
Source: "my-instance",
Description: "some description",
AuthRequired: []string{},
},
},
},
}
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)
}
})
}
}

View File

@@ -0,0 +1,215 @@
// 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 searchdicomstudies
import (
"context"
"encoding/json"
"fmt"
"io"
"github.com/goccy/go-yaml"
"github.com/googleapis/genai-toolbox/internal/sources"
healthcareds "github.com/googleapis/genai-toolbox/internal/sources/cloudhealthcare"
"github.com/googleapis/genai-toolbox/internal/tools"
"github.com/googleapis/genai-toolbox/internal/tools/cloudhealthcare/common"
"google.golang.org/api/healthcare/v1"
)
const kind string = "cloud-healthcare-search-dicom-studies"
const (
studyInstanceUIDKey = "StudyInstanceUID"
patientNameKey = "PatientName"
patientIDKey = "PatientID"
accessionNumberKey = "AccessionNumber"
referringPhysicianNameKey = "ReferringPhysicianName"
studyDateKey = "StudyDate"
)
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 compatibleSource interface {
Project() string
Region() string
DatasetID() string
AllowedDICOMStores() map[string]struct{}
Service() *healthcare.Service
ServiceCreator() healthcareds.HealthcareServiceCreator
UseClientAuthorization() bool
}
// validate compatible sources are still compatible
var _ compatibleSource = &healthcareds.Source{}
var compatibleSources = [...]string{healthcareds.SourceKind}
type Config struct {
Name string `yaml:"name" validate:"required"`
Kind string `yaml:"kind" validate:"required"`
Source string `yaml:"source" validate:"required"`
Description string `yaml:"description" validate:"required"`
AuthRequired []string `yaml:"authRequired"`
}
// 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.(compatibleSource)
if !ok {
return nil, fmt.Errorf("invalid source for %q tool: source kind must be one of %q", kind, compatibleSources)
}
parameters := tools.Parameters{
tools.NewStringParameterWithDefault(studyInstanceUIDKey, "", "The UID of the DICOM study"),
tools.NewStringParameterWithDefault(patientNameKey, "", "The name of the patient"),
tools.NewStringParameterWithDefault(patientIDKey, "", "The ID of the patient"),
tools.NewStringParameterWithDefault(accessionNumberKey, "", "The accession number of the study"),
tools.NewStringParameterWithDefault(referringPhysicianNameKey, "", "The name of the referring physician"),
tools.NewStringParameterWithDefault(studyDateKey, "", "The date of the study in the format `YYYYMMDD`. You can also specify a date range in the format `YYYYMMDD-YYYYMMDD`"),
tools.NewBooleanParameterWithDefault(common.EnablePatientNameFuzzyMatchingKey, false, `Whether to enable fuzzy matching for patient names. Fuzzy matching will perform tokenization and normalization of both the value of PatientName in the query and the stored value. It will match if any search token is a prefix of any stored token. For example, if PatientName is "John^Doe", then "jo", "Do" and "John Doe" will all match. However "ohn" will not match`),
tools.NewArrayParameterWithDefault(common.IncludeAttributesKey, []any{}, "List of attributeIDs, such as DICOM tag IDs or keywords. Set to [\"all\"] to return all available tags.", tools.NewStringParameter("attributeID", "The attributeID to include. Set to 'all' to return all available tags")),
}
if len(s.AllowedDICOMStores()) != 1 {
parameters = append(parameters, tools.NewStringParameter(common.StoreKey, "The DICOM store ID to get details for."))
}
mcpManifest := tools.GetMcpManifest(cfg.Name, cfg.Description, cfg.AuthRequired, parameters)
// finish tool setup
t := Tool{
Name: cfg.Name,
Kind: kind,
Parameters: parameters,
AuthRequired: cfg.AuthRequired,
Project: s.Project(),
Region: s.Region(),
Dataset: s.DatasetID(),
AllowedStores: s.AllowedDICOMStores(),
UseClientOAuth: s.UseClientAuthorization(),
ServiceCreator: s.ServiceCreator(),
Service: s.Service(),
manifest: tools.Manifest{Description: cfg.Description, Parameters: parameters.Manifest(), AuthRequired: cfg.AuthRequired},
mcpManifest: mcpManifest,
}
return t, nil
}
// validate interface
var _ tools.Tool = Tool{}
type Tool struct {
Name string `yaml:"name"`
Kind string `yaml:"kind"`
AuthRequired []string `yaml:"authRequired"`
UseClientOAuth bool `yaml:"useClientOAuth"`
Parameters tools.Parameters `yaml:"parameters"`
Project, Region, Dataset string
AllowedStores map[string]struct{}
Service *healthcare.Service
ServiceCreator healthcareds.HealthcareServiceCreator
manifest tools.Manifest
mcpManifest tools.McpManifest
}
func (t Tool) Invoke(ctx context.Context, params tools.ParamValues, accessToken tools.AccessToken) (any, error) {
storeID, err := common.ValidateAndFetchStoreID(params, t.AllowedStores)
if err != nil {
return nil, err
}
svc := t.Service
// Initialize new service if using user OAuth token
if t.UseClientOAuth {
tokenStr, err := accessToken.ParseBearerToken()
if err != nil {
return nil, fmt.Errorf("error parsing access token: %w", err)
}
svc, err = t.ServiceCreator(tokenStr)
if err != nil {
return nil, fmt.Errorf("error creating service from OAuth access token: %w", err)
}
}
opts, err := common.ParseDICOMSearchParameters(params, []string{studyInstanceUIDKey, patientNameKey, patientIDKey, accessionNumberKey, referringPhysicianNameKey, studyDateKey})
if err != nil {
return nil, err
}
name := fmt.Sprintf("projects/%s/locations/%s/datasets/%s/dicomStores/%s", t.Project, t.Region, t.Dataset, storeID)
resp, err := svc.Projects.Locations.Datasets.DicomStores.SearchForStudies(name, "studies").Do(opts...)
if err != nil {
return nil, fmt.Errorf("failed to search dicom studies: %w", err)
}
defer resp.Body.Close()
respBytes, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("could not read response: %w", err)
}
if resp.StatusCode > 299 {
return nil, fmt.Errorf("search: status %d %s: %s", resp.StatusCode, resp.Status, respBytes)
}
if len(respBytes) == 0 {
return []interface{}{}, nil
}
var result []interface{}
if err := json.Unmarshal([]byte(string(respBytes)), &result); err != nil {
return nil, fmt.Errorf("could not unmarshal response as list: %w", err)
}
return result, nil
}
func (t Tool) ParseParams(data map[string]any, claims map[string]map[string]any) (tools.ParamValues, error) {
return tools.ParseParams(t.Parameters, 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)
}
func (t Tool) RequiresClientAuthorization() bool {
return t.UseClientOAuth
}

View File

@@ -0,0 +1,72 @@
// 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 searchdicomstudies_test
import (
"testing"
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"
searchdicomstudies "github.com/googleapis/genai-toolbox/internal/tools/cloudhealthcare/cloudhealthcaresearchdicomstudies"
)
func TestParseFromYamlHealthcareSearchDICOMStudies(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: cloud-healthcare-search-dicom-studies
source: my-instance
description: some description
`,
want: server.ToolConfigs{
"example_tool": searchdicomstudies.Config{
Name: "example_tool",
Kind: "cloud-healthcare-search-dicom-studies",
Source: "my-instance",
Description: "some description",
AuthRequired: []string{},
},
},
},
}
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)
}
})
}
}

View File

@@ -0,0 +1,91 @@
// 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 common
import (
"fmt"
"slices"
"strings"
"github.com/googleapis/genai-toolbox/internal/tools"
"google.golang.org/api/googleapi"
)
// StoreKey is the key used to identify FHIR/DICOM store IDs in tool parameters.
const StoreKey = "storeID"
// EnablePatientNameFuzzyMatchingKey is the key used for DICOM search to enable
// fuzzy matching.
const EnablePatientNameFuzzyMatchingKey = "fuzzymatching"
// IncludeAttributesKey is the key used for DICOM search to include additional
// tags in the response.
const IncludeAttributesKey = "includefield"
// ValidateAndFetchStoreID validates the provided storeID against the allowedStores.
// If only one store is allowed, it returns that storeID.
// If multiple stores are allowed, it checks if the storeID parameter is in the allowed list.
func ValidateAndFetchStoreID(params tools.ParamValues, allowedStores map[string]struct{}) (string, error) {
if len(allowedStores) == 1 {
for k := range allowedStores {
return k, nil
}
}
mapParams := params.AsMap()
storeID, ok := mapParams[StoreKey].(string)
if !ok {
return "", fmt.Errorf("invalid or missing '%s' parameter; expected a string", StoreKey)
}
if len(allowedStores) > 0 {
if _, ok := allowedStores[storeID]; !ok {
return "", fmt.Errorf("store ID '%s' is not in the list of allowed stores", storeID)
}
}
return storeID, nil
}
// ParseDICOMSearchParameters extracts the search parameters for various DICOM
// search methods.
func ParseDICOMSearchParameters(params tools.ParamValues, paramKeys []string) ([]googleapi.CallOption, error) {
var opts []googleapi.CallOption
for k, v := range params.AsMap() {
if k == IncludeAttributesKey {
if _, ok := v.([]any); !ok {
return nil, fmt.Errorf("invalid '%s' parameter; expected a string array", k)
}
attributeIDsSlice, err := tools.ConvertAnySliceToTyped(v.([]any), "string")
if err != nil {
return nil, fmt.Errorf("can't convert '%s' to array of strings: %s", k, err)
}
attributeIDs := attributeIDsSlice.([]string)
if len(attributeIDs) != 0 {
opts = append(opts, googleapi.QueryParameter(k, strings.Join(attributeIDs, ",")))
}
} else if k == EnablePatientNameFuzzyMatchingKey {
if _, ok := v.(bool); !ok {
return nil, fmt.Errorf("invalid '%s' parameter; expected a boolean", k)
}
opts = append(opts, googleapi.QueryParameter(k, fmt.Sprintf("%t", v.(bool))))
} else if slices.Contains(paramKeys, k) {
if _, ok := v.(string); !ok {
return nil, fmt.Errorf("invalid '%s' parameter; expected a string", k)
}
if v.(string) != "" {
opts = append(opts, googleapi.QueryParameter(k, v.(string)))
}
}
}
return opts, nil
}

File diff suppressed because it is too large Load Diff