feat(mcp): add documentation endpoint (#7331)

* simplify inkeep endpoint + add to openapi

* adapt frontend

* add to mcp endpoints

* nit

* udpate ref
This commit is contained in:
centdix
2025-12-10 17:28:56 +00:00
committed by GitHub
parent d2dc03bc0e
commit 2a0c4cd1c9
6 changed files with 252 additions and 53 deletions
+1 -1
View File
@@ -1 +1 @@
8f6e0bd9f273ac9da5f4fb3a44979f17b8e1b9b5
ffc3e065de448f94d2cd6b39d4aac917e1a77307
+42 -7
View File
@@ -69,6 +69,41 @@ paths:
schema:
type: string
/inkeep:
post:
summary: query Windmill AI documentation assistant (EE only)
operationId: queryDocumentation
x-mcp-tool: true
tags:
- documentation
requestBody:
description: query to send to the AI documentation assistant
required: true
content:
application/json:
schema:
type: object
properties:
query:
type: string
description: The documentation query to send to the AI assistant
required:
- query
responses:
"200":
description: AI documentation assistant response
content:
application/json:
schema:
type: object
description: Response from Inkeep service
"403":
description: Enterprise Edition required
content:
text/plain:
schema:
type: string
/openapi.yaml:
get:
summary: get openapi yaml spec
@@ -8930,9 +8965,9 @@ paths:
"200":
description: confirmation message
content:
application/json:
schema:
type: string
application/json:
schema:
type: string
/w/{workspace}/trigger/{trigger_kind}/cancel_suspended_trigger_jobs/{trigger_path}:
post:
@@ -16643,7 +16678,7 @@ components:
"aiagent",
"unassigned_script",
"unassigned_flow",
"unassigned_singlestepflow"
"unassigned_singlestepflow",
]
schedule_path:
type: string
@@ -16759,7 +16794,7 @@ components:
"aiagent",
"unassigned_script",
"unassigned_flow",
"unassigned_singlestepflow"
"unassigned_singlestepflow",
]
schedule_path:
type: string
@@ -16883,7 +16918,7 @@ components:
"aiagent",
"unassigned_script",
"unassigned_flow",
"unassigned_singlestepflow"
"unassigned_singlestepflow",
]
trigger:
type: string
@@ -17031,7 +17066,7 @@ components:
"aiagent",
"unassigned_script",
"unassigned_flow",
"unassigned_singlestepflow"
"unassigned_singlestepflow",
]
trigger:
type: string
+14 -2
View File
@@ -3,9 +3,21 @@
pub use crate::inkeep_ee::*;
#[cfg(not(feature = "private"))]
use axum::Router;
use axum::{routing::post, Router};
#[cfg(not(feature = "private"))]
use windmill_common::error::Error;
#[cfg(not(feature = "private"))]
pub fn global_service() -> Router {
Router::new()
Router::new().route("/", post(inkeep_not_available))
}
#[cfg(not(feature = "private"))]
async fn inkeep_not_available() -> windmill_common::error::Result<()> {
Err(Error::Generic(
http::StatusCode::FORBIDDEN,
"Inkeep AI documentation assistant is only available in Windmill Enterprise Edition"
.to_string(),
))
}
@@ -20,6 +20,27 @@ pub struct EndpointTool {
pub fn all_tools() -> Vec<EndpointTool> {
vec![
EndpointTool {
name: Cow::Borrowed("queryDocumentation"),
description: Cow::Borrowed("query Windmill AI documentation assistant (EE only)"),
instructions: Cow::Borrowed(""),
path: Cow::Borrowed("/inkeep"),
method: Cow::Borrowed("POST"),
path_params_schema: None,
query_params_schema: None,
body_schema: Some(serde_json::json!({
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "The documentation query to send to the AI assistant"
}
},
"required": [
"query"
]
})),
},
EndpointTool {
name: Cow::Borrowed("createVariable"),
description: Cow::Borrowed("create variable"),
@@ -447,6 +468,10 @@ pub fn all_tools() -> Vec<EndpointTool> {
"languages": {
"type": "string",
"description": "Filter to only include scripts written in the given languages.\nAccepts multiple values as a comma-separated list.\n"
},
"without_description": {
"type": "boolean",
"description": "(default false)\nIf true, the description field will be omitted from the response.\n"
}
},
"required": []
@@ -530,6 +555,10 @@ pub fn all_tools() -> Vec<EndpointTool> {
"with_deployment_msg": {
"type": "boolean",
"description": "(default false)\ninclude deployment message\n"
},
"without_description": {
"type": "boolean",
"description": "(default false)\nIf true, the description field will be omitted from the response.\n"
}
},
"required": []
@@ -683,6 +712,28 @@ pub fn all_tools() -> Vec<EndpointTool> {
"type": "string",
"description": "mask to filter by schedule path"
},
"trigger_path": {
"type": "string",
"description": "mask to filter by trigger path"
},
"trigger_kind": {
"description": "trigger kind (schedule, http, websocket...)",
"type": "string",
"enum": [
"webhook",
"default_email",
"email",
"schedule",
"http",
"websocket",
"postgres",
"kafka",
"nats",
"mqtt",
"sqs",
"gcp"
]
},
"script_hash": {
"type": "string",
"description": "mask to filter exact matching path"
@@ -817,10 +868,25 @@ pub fn all_tools() -> Vec<EndpointTool> {
"format": "date-time",
"description": "filter on created after (exclusive) timestamp"
},
"created_or_started_before": {
"completed_before": {
"type": "string",
"format": "date-time",
"description": "filter on created_at for non non started job and started_at otherwise before (inclusive) timestamp"
"description": "filter on started before (inclusive) timestamp"
},
"completed_after": {
"type": "string",
"format": "date-time",
"description": "filter on started after (exclusive) timestamp"
},
"created_before_queue": {
"type": "string",
"format": "date-time",
"description": "filter on jobs created before X for jobs in the queue only"
},
"created_after_queue": {
"type": "string",
"format": "date-time",
"description": "filter on jobs created after X for jobs in the queue only"
},
"running": {
"type": "boolean",
@@ -830,16 +896,6 @@ pub fn all_tools() -> Vec<EndpointTool> {
"type": "boolean",
"description": "filter on jobs scheduled_for before now (hence waitinf for a worker)"
},
"created_or_started_after": {
"type": "string",
"format": "date-time",
"description": "filter on created_at for non non started job and started_at otherwise after (exclusive) timestamp"
},
"created_or_started_after_completed_jobs": {
"type": "string",
"format": "date-time",
"description": "filter on created_at for non non started job and started_at otherwise after (exclusive) timestamp but only for the completed jobs"
},
"job_kinds": {
"type": "string",
"description": "filter on job kind (values 'preview', 'script', 'dependencies', 'flow') separated by,"
@@ -864,14 +920,28 @@ pub fn all_tools() -> Vec<EndpointTool> {
"type": "boolean",
"description": "allow wildcards (*) in the filter of label, tag, worker"
},
"page": {
"type": "integer",
"description": "which page to return (start at 1, default 1)"
},
"per_page": {
"type": "integer",
"description": "number of items to return for a given page (default 30, max 100)"
},
"trigger_kind": {
"description": "trigger kind (schedule, http, websocket...)",
"type": "string",
"enum": [
"webhook",
"default_email",
"email",
"schedule",
"http",
"websocket",
"postgres",
"kafka",
"nats",
"mqtt",
"sqs",
"gcp"
]
},
"is_skipped": {
"type": "boolean",
"description": "is the job skipped"
@@ -235,13 +235,15 @@ async function getDocumentation(args: { request: string }): Promise<string> {
'Content-Type': 'application/json'
},
body: JSON.stringify({
model: 'inkeep-rag',
messages: [{ role: 'user', content: args.request }],
response_format: {
type: 'json_object'
}
query: args.request
})
})
if (!retrieval.ok) {
const errorText = await retrieval.text()
throw new Error(errorText)
}
const data = await retrieval.json()
if (!data.choices?.[0]?.message?.content) {
return 'No documentation found for this request'
@@ -291,11 +293,11 @@ const getTriggerableComponentsTool: Tool<{}> = {
def: GET_TRIGGERABLE_COMPONENTS_TOOL,
fn: async ({ toolId, toolCallbacks }) => {
toolCallbacks.setToolStatus(toolId, {
content: 'Scanning the page...',
content: 'Scanning the page...'
})
const components = getTriggerableComponents()
toolCallbacks.setToolStatus(toolId, {
content: 'Scanned the page',
content: 'Scanned the page'
})
return components
}
@@ -319,9 +321,14 @@ export const getDocumentationTool: Tool<{}> = {
toolCallbacks.setToolStatus(toolId, { content: 'Retrieved documentation' })
return docResult
} catch (error) {
toolCallbacks.setToolStatus(toolId, { content: 'Error getting documentation', error: 'Error getting documentation' })
toolCallbacks.setToolStatus(toolId, {
content: 'Error getting documentation',
error: 'Error getting documentation'
})
console.error('Error getting documentation:', error)
return 'Failed to get documentation, pursuing with the user request...'
const errorMessage =
error instanceof Error ? error.message : 'An error occurred while getting documentation'
return `Failed to get documentation: ${errorMessage}, pursuing with the user request...`
}
}
}
@@ -335,7 +342,10 @@ const getAvailableResourcesTool: Tool<{}> = {
toolCallbacks.setToolStatus(toolId, { content: 'Retrieved available resources' })
return resources
} catch (error) {
toolCallbacks.setToolStatus(toolId, { content: 'Error getting available resources', error: 'Error getting available resources' })
toolCallbacks.setToolStatus(toolId, {
content: 'Error getting available resources',
error: 'Error getting available resources'
})
console.error('Error getting available resources:', error)
return 'Failed to get available resources, pursuing with the user request...'
}
@@ -350,7 +360,9 @@ export const navigatorTools: Tool<{}>[] = [
getAvailableResourcesTool
]
export function prepareNavigatorSystemMessage(customPrompt?: string): ChatCompletionSystemMessageParam {
export function prepareNavigatorSystemMessage(
customPrompt?: string
): ChatCompletionSystemMessageParam {
let content = CHAT_SYSTEM_PROMPT
// If there's a custom prompt, append it to the system prompt
+86 -16
View File
@@ -13,6 +13,27 @@ export interface EndpointTool {
}
export const mcpEndpointTools: EndpointTool[] = [
{
name: "queryDocumentation",
description: "query Windmill AI documentation assistant (EE only)",
instructions: "",
path: "/inkeep",
method: "POST",
pathParamsSchema: undefined,
queryParamsSchema: undefined,
bodySchema: {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "The documentation query to send to the AI assistant"
}
},
"required": [
"query"
]
}
},
{
name: "createVariable",
description: "create variable",
@@ -440,6 +461,10 @@ export const mcpEndpointTools: EndpointTool[] = [
"languages": {
"type": "string",
"description": "Filter to only include scripts written in the given languages.\nAccepts multiple values as a comma-separated list.\n"
},
"without_description": {
"type": "boolean",
"description": "(default false)\nIf true, the description field will be omitted from the response.\n"
}
},
"required": []
@@ -523,6 +548,10 @@ export const mcpEndpointTools: EndpointTool[] = [
"with_deployment_msg": {
"type": "boolean",
"description": "(default false)\ninclude deployment message\n"
},
"without_description": {
"type": "boolean",
"description": "(default false)\nIf true, the description field will be omitted from the response.\n"
}
},
"required": []
@@ -676,6 +705,28 @@ export const mcpEndpointTools: EndpointTool[] = [
"type": "string",
"description": "mask to filter by schedule path"
},
"trigger_path": {
"type": "string",
"description": "mask to filter by trigger path"
},
"trigger_kind": {
"description": "trigger kind (schedule, http, websocket...)",
"type": "string",
"enum": [
"webhook",
"default_email",
"email",
"schedule",
"http",
"websocket",
"postgres",
"kafka",
"nats",
"mqtt",
"sqs",
"gcp"
]
},
"script_hash": {
"type": "string",
"description": "mask to filter exact matching path"
@@ -810,10 +861,25 @@ export const mcpEndpointTools: EndpointTool[] = [
"format": "date-time",
"description": "filter on created after (exclusive) timestamp"
},
"created_or_started_before": {
"completed_before": {
"type": "string",
"format": "date-time",
"description": "filter on created_at for non non started job and started_at otherwise before (inclusive) timestamp"
"description": "filter on started before (inclusive) timestamp"
},
"completed_after": {
"type": "string",
"format": "date-time",
"description": "filter on started after (exclusive) timestamp"
},
"created_before_queue": {
"type": "string",
"format": "date-time",
"description": "filter on jobs created before X for jobs in the queue only"
},
"created_after_queue": {
"type": "string",
"format": "date-time",
"description": "filter on jobs created after X for jobs in the queue only"
},
"running": {
"type": "boolean",
@@ -823,16 +889,6 @@ export const mcpEndpointTools: EndpointTool[] = [
"type": "boolean",
"description": "filter on jobs scheduled_for before now (hence waitinf for a worker)"
},
"created_or_started_after": {
"type": "string",
"format": "date-time",
"description": "filter on created_at for non non started job and started_at otherwise after (exclusive) timestamp"
},
"created_or_started_after_completed_jobs": {
"type": "string",
"format": "date-time",
"description": "filter on created_at for non non started job and started_at otherwise after (exclusive) timestamp but only for the completed jobs"
},
"job_kinds": {
"type": "string",
"description": "filter on job kind (values 'preview', 'script', 'dependencies', 'flow') separated by,"
@@ -857,14 +913,28 @@ export const mcpEndpointTools: EndpointTool[] = [
"type": "boolean",
"description": "allow wildcards (*) in the filter of label, tag, worker"
},
"page": {
"type": "integer",
"description": "which page to return (start at 1, default 1)"
},
"per_page": {
"type": "integer",
"description": "number of items to return for a given page (default 30, max 100)"
},
"trigger_kind": {
"description": "trigger kind (schedule, http, websocket...)",
"type": "string",
"enum": [
"webhook",
"default_email",
"email",
"schedule",
"http",
"websocket",
"postgres",
"kafka",
"nats",
"mqtt",
"sqs",
"gcp"
]
},
"is_skipped": {
"type": "boolean",
"description": "is the job skipped"