Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "parseable"
version = "3.2.4"
version = "3.2.5"
authors = ["Parseable Team <hi@parseable.com>"]
edition = "2024"
rust-version = "1.94.0"
Expand Down Expand Up @@ -234,8 +234,8 @@ arrow = "59.2.0"
temp-dir = "0.1.14"

[package.metadata.parseable_ui]
assets-url = "https://parseableprismbuild.blob.core.windows.net/parseable-prism-build/v3.2.4/build.zip"
assets-sha1 = "5e3ec81fbb35b2b8aa6486a2bd38257d46b0264b"
assets-url = "https://parseableprismbuild.blob.core.windows.net/parseable-prism-build/v3.2.5/build.zip"
assets-sha1 = "cbaf98f0431844677d30f519b3da941599eb2e0a"

[features]
debug = []
Expand Down
91 changes: 91 additions & 0 deletions docs/tool-api.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
# Parseable tool REST API

Parseable owns the canonical tool catalog and execution semantics. External MCP
servers adapt these REST endpoints to MCP `tools/list` and `tools/call`.

Both endpoints require normal Parseable authentication. Tool execution also
applies the RBAC action required by the selected tool and any referenced
dataset or resource.

## List tools

```http
GET /api/prism/v1/llm/tools/list
```

Response:

```json
{
"tools": [
{
"name": "get_dataset_schema",
"title": "Get dataset schema",
"description": "Get columns and data types for a dataset.",
"inputSchema": {
"type": "object",
"properties": { "dataset": { "type": "string", "minLength": 1 } },
"required": ["dataset"],
"additionalProperties": false
}
}
]
}
```

The response includes an `ETag` and
`Cache-Control: private, max-age=300, must-revalidate`. A request with a
matching `If-None-Match` receives `304 Not Modified`.

The catalog is edition-specific:

- OSS returns externally callable OSS read tools.
- Enterprise returns OSS read tools plus Enterprise read tools.
- Parseable Cloud uses the Enterprise server catalog and exposes the tools
enabled by that deployment.

## Call a tool

```http
POST /api/prism/v1/llm/tools/call
Content-Type: application/json
```

Request:

```json
{
"name": "get_dataset_schema",
"arguments": { "dataset": "application-logs" }
}
```

`arguments` must satisfy the tool's `inputSchema`. The external registry
currently exposes only read-only and expensive-read tools. Mutation and
external-side-effect tools remain unavailable until Parseable supports a
server-verifiable confirmation flow bound to the session, tool, and arguments.

Successful and failed executions use the MCP `CallToolResult` shape:

```json
{
"content": [
{ "type": "text", "text": "{\"fields\":[...]}" }
],
"structuredContent": {
"result": { "fields": [] }
},
"isError": false
}
```

Tool failures set `isError` to `true`, include a text error content item, and
put the message under `structuredContent.error.message`. A completed tool call
returns HTTP 200 even when the tool reports `isError: true`, matching MCP
`tools/call` semantics. REST status codes distinguish failures that prevent a
call from starting, such as malformed input, authentication, or an unknown
tool.

The REST API is not itself an MCP transport. An MCP adapter maps the registry
response to `tools/list`, forwards calls to this endpoint, and returns the
`CallToolResult` from Parseable through MCP `tools/call`.
22 changes: 15 additions & 7 deletions src/handlers/http/modal/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -202,13 +202,21 @@ impl ParseableServer for Server {

impl Server {
pub fn get_llm_webscope() -> Scope {
web::scope("/llm").service(
web::resource("/tools").route(
web::get()
.to(crate::tool_catalog::list_tool_registry)
.authorize(Action::Query),
),
)
web::scope("/llm")
.service(
web::resource("/tools/list").route(
web::get()
.to(crate::tool_catalog::list_tool_registry)
.authorize(Action::Query),
),
)
.service(
web::resource("/tools/call").route(
web::post()
.to(crate::tool_executor::call_tool_registry)
.authorize(Action::Query),
),
)
}

pub fn get_prism_home() -> Scope {
Expand Down
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ pub mod sync;
pub mod telemetry;
pub mod tenants;
pub mod tool_catalog;
pub mod tool_executor;
pub mod users;
pub mod utils;
pub mod validator;
Expand Down
21 changes: 13 additions & 8 deletions src/sync.rs
Original file line number Diff line number Diff line change
Expand Up @@ -451,20 +451,24 @@ pub async fn alert_runtime(mut rx: mpsc::Receiver<AlertTask>) -> Result<(), anyh
let id = *alert.get_id();
let handle = tokio::spawn(async move {
let mut retry_counter = 0;
let mut sleep_duration = alert.get_eval_frequency();
loop {
match alerts_utils::evaluate_alert(&*alert).await {
let sleep_duration = match alerts_utils::evaluate_alert(&*alert).await {
Ok(_) => {
retry_counter = 0;
alert.get_eval_frequency()
}
Err(err) => {
let retry_delay = if alert.get_query_type()
== crate::alerts::AlertQueryType::Promql
{
alert.get_eval_frequency()
} else {
1
};
warn!(
"Error while evaluation- {}\nRetrying after sleeping for 1 minute",
err
"Error while evaluation- {}\nRetrying after sleeping for {} minute(s)",
err, retry_delay
);
if alert.get_query_type() != crate::alerts::AlertQueryType::Promql {
sleep_duration = 1;
}
retry_counter += 1;

if retry_counter > 3
Expand All @@ -477,8 +481,9 @@ pub async fn alert_runtime(mut rx: mpsc::Receiver<AlertTask>) -> Result<(), anyh
);
break;
}
retry_delay
}
}
};
tokio::time::sleep(Duration::from_secs(sleep_duration * 60)).await;
}
});
Expand Down
Loading
Loading