Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
b42f175
feat(indexer): scaffold mcp_tools indexer with settings
theyashl May 25, 2026
728e2b9
feat(indexer): _load_servers_from_setting parses MCP_CONFIG with vali…
theyashl May 25, 2026
283cf27
feat(indexer): _list_tools performs MCP handshake and returns tool array
theyashl May 25, 2026
1d3e533
feat(indexer): _emit_tool_resource + index() drift-preserving on failure
theyashl May 25, 2026
60cc52e
feat(workspace-builder): register mcp_tools indexer in component pipe…
theyashl May 25, 2026
22d4c8c
test(indexer): end-to-end mcp_tools to template render integration test
theyashl May 25, 2026
142c240
test(indexer): guard runtimeVarsProvided field allowlist
theyashl May 25, 2026
3a2bc8e
test(indexer): assert every runtimeVar has validation with allowed type
theyashl May 25, 2026
992f5d1
feat(mcp): register McpPlatformHandler so gen rules can load
theyashl May 26, 2026
7457225
chore(mcp): trace indexer execution end-to-end
theyashl May 26, 2026
29b3f82
fix(mcp): add mcp_tools to the run.sh and run.py components allowlist
theyashl May 26, 2026
d8caa0a
fix(mcp): forward mcpConfig from workspaceInfo into the REST request …
theyashl May 26, 2026
a0f9928
feat(mcp): add per-server verify_tls escape hatch
theyashl May 26, 2026
6f20811
fix(mcp): pass verify per-request to defeat REQUESTS_CA_BUNDLE override
theyashl May 26, 2026
7c3b850
fix(mcp): base64-decode secret value before using as bearer token
theyashl May 26, 2026
dd41f1e
chore: re-trigger CI for 40361e3 (gh actions 500'd on the previous push)
theyashl May 26, 2026
33217c7
fix(mcp): parse SSE (text/event-stream) responses from MCP servers
theyashl May 26, 2026
71d322e
docs(mcp): document mcpConfig block in workspaceInfo customization guide
theyashl May 26, 2026
8bb83fe
chore: nudge CI now that gh actions is healthy again
theyashl May 26, 2026
f81a5fd
feat(mcp): thread codecollection_ref from mcpConfig into mcp_tool res…
theyashl May 26, 2026
aa608de
docs(mcp): document codecollection_ref field in mcpConfig server entries
theyashl May 26, 2026
a71dda9
revert(mcp): drop codecollection_ref from mcpConfig — wrong abstraction
theyashl May 26, 2026
d3f5b0d
feat(indexer): forward mcpConfig verify_tls into mcp_tool resource spec
theyashl May 27, 2026
bb8ecda
feat(mcp-tools): classify per-tool access via readOnlyHint + verb heu…
theyashl May 28, 2026
4672265
test(mcp-tools): assert MCP_SERVER_DISPLAY_NAME in rendered configPro…
theyashl May 28, 2026
fd34cc9
fix(render): honor explicit resourcePath in template, skip auto-compute
theyashl May 28, 2026
0d9cb8e
test(mcp): lock in runtimeVar default string coercion
theyashl May 29, 2026
0e6dffa
test(mcp): lock in runtimeVar required-param description marking
theyashl Jun 1, 2026
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
27 changes: 27 additions & 0 deletions docs/configuration/workspaceinfo-customization.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,39 @@ codeCollections:
- # Another code collections to scan


# Information about MCP servers to discover tools from
mcpConfig:
servers:
- display_name: jira
url: https://jira-mcp.internal:443/mcp
secret_ref: jira-mcp-token


# Custom information about specific code bundles
custom:
prometheus_provider: gmp
# More custom configuration
```

#### MCP Server Discovery (`mcpConfig`)

The optional `mcpConfig` block declares one or more private MCP servers to introspect during the indexer phase. For each entry the workspace builder calls `initialize` + `tools/list` on the server, then emits one `mcp_tool` resource per discovered tool. The `mcp-tool-proxy` codebundle in [rw-generic-codecollection](https://github.com/runwhen-contrib/rw-generic-codecollection) renders one SLX + Runbook per `mcp_tool` resource via its generation rule.

<table><thead><tr><th width="220">Field</th><th width="120">Required</th><th>Description</th></tr></thead><tbody><tr><td><code>display_name</code></td><td>yes</td><td>Short identifier used in generated SLX names and tags (e.g. <code>jira</code>, <code>linear</code>). Lowercase, alphanumeric / underscores.</td></tr><tr><td><code>url</code></td><td>yes</td><td>Full HTTPS URL of the MCP endpoint, including the path (commonly <code>/mcp</code>). Must be reachable from the workspace-builder pod's network at index time, <em>and</em> from runner pods at execution time.</td></tr><tr><td><code>secret_ref</code></td><td>yes</td><td>Name of a Kubernetes <code>Secret</code> in the same namespace whose <code>data.token</code> is the bearer token to send as <code>Authorization: Bearer &lt;token&gt;</code>. Token is base64-decoded by the indexer before use.</td></tr><tr><td><code>verify_tls</code></td><td>no (default <code>true</code>)</td><td>Set to <code>false</code> to skip TLS certificate verification for this server. Intended for environments where the pod's CA bundle does not yet trust the server's issuer; a warning is logged for every cycle in which it is disabled. Prefer extending the CA bundle for production use.</td></tr></tbody></table>

> The generated Runbook's <code>codeBundle.ref</code> is pinned to the ref of the codecollection the template was loaded from (the <code>ref</code> standard template variable). Point your <code>codeCollections</code> entry for <code>rw-generic-codecollection</code> at the branch/tag you want runners to execute against — no extra knob in <code>mcpConfig</code>.

A single server's `tools/list` failure logs a warning and skips that server only — other servers and the rest of the cycle continue normally.

##### Required secret shape

```bash
kubectl -n <namespace> create secret generic jira-mcp-token \
--from-literal=token='<bearer-token-value>'
```

The runner uses the same `secret_ref` at execution time (via `secretsProvided.workspaceKey`), so the secret must be reachable from the runner namespace too.

#### Basic Workspace Configuration Info

There are several settings that are used to configure information in the workspace that's generated to be uploaded to the RunWhen platform to. The available settings are:
Expand Down
2 changes: 1 addition & 1 deletion src/component.py
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,7 @@ def init_components():
# be added here, which is less than ideal, although practically may not be
# a huge deal.
component_stages_init = (
(Stage.INDEXER, ["load_resources", "kubeapi", "cloudquery", "azure_devops"]),
(Stage.INDEXER, ["load_resources", "kubeapi", "cloudquery", "azure_devops", "mcp_tools"]),
(Stage.ENRICHER, ["generation_rules"]),
(Stage.RENDERER, ["render_output_items", "dump_resources"])
)
Expand Down
2 changes: 2 additions & 0 deletions src/enrichers/generation_rules.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
from .gcp import GCPPlatformHandler, GCP_PLATFORM
from .aws import AWSPlatformHandler, AWS_PLATFORM
from .azure_devops import AzureDevOpsPlatformHandler
from .mcp import McpPlatformHandler, MCP_PLATFORM

from renderers.render_output_items import OUTPUT_ITEMS_PROPERTY
from renderers.render_output_items import OutputItem as RendererOutputItem
Expand Down Expand Up @@ -1110,6 +1111,7 @@ def load(context: Context) -> None:
GCP_PLATFORM: GCPPlatformHandler(),
AWS_PLATFORM: AWSPlatformHandler(),
"azure_devops": AzureDevOpsPlatformHandler(),
MCP_PLATFORM: McpPlatformHandler(),
}
context.set_property(PLATFORM_HANDLERS_PROPERTY_NAME, platform_handlers)
request_code_collections = context.get_setting("CODE_COLLECTIONS")
Expand Down
35 changes: 35 additions & 0 deletions src/enrichers/mcp.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
from typing import Any, Optional

from resources import Resource

from .generation_rule_types import PlatformHandler

# Source of truth for the platform name lives with the indexer; mirror it
# here for the enricher's registration without forcing a cross-package
# dependency at the top level.
from indexers.mcp_tools import PLATFORM_NAME as MCP_PLATFORM


class McpPlatformHandler(PlatformHandler):
"""Platform handler for MCP-discovered tool resources.

Resources are emitted by `indexers.mcp_tools._emit_tool_resource` with a
nested `spec` dict carrying the server/tool fields. This handler surfaces
those fields as qualifier values and resource property values so the
generic generation-rules engine can match and name SLXs against them.
"""

def __init__(self):
super().__init__(MCP_PLATFORM)

def _spec_value(self, resource: Resource, key: str) -> Optional[str]:
spec = getattr(resource, "spec", None) or {}
value = spec.get(key)
return value if value is not None else None

def get_resource_qualifier_value(self, resource: Resource, qualifier_name: str) -> Optional[str]:
return self._spec_value(resource, qualifier_name)

def get_resource_property_values(self, resource: Resource, property_name: str) -> Optional[list[Any]]:
value = self._spec_value(resource, property_name)
return [value] if value is not None else None
Loading
Loading