iflow-mcp_niclasolofsson-dbt-core-mcp 1.7.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- dbt_core_mcp/__init__.py +18 -0
- dbt_core_mcp/__main__.py +436 -0
- dbt_core_mcp/context.py +459 -0
- dbt_core_mcp/cte_generator.py +601 -0
- dbt_core_mcp/dbt/__init__.py +1 -0
- dbt_core_mcp/dbt/bridge_runner.py +1361 -0
- dbt_core_mcp/dbt/manifest.py +781 -0
- dbt_core_mcp/dbt/runner.py +67 -0
- dbt_core_mcp/dependencies.py +50 -0
- dbt_core_mcp/server.py +381 -0
- dbt_core_mcp/tools/__init__.py +77 -0
- dbt_core_mcp/tools/analyze_impact.py +78 -0
- dbt_core_mcp/tools/build_models.py +190 -0
- dbt_core_mcp/tools/demo/__init__.py +1 -0
- dbt_core_mcp/tools/demo/hello.html +267 -0
- dbt_core_mcp/tools/demo/ui_demo.py +41 -0
- dbt_core_mcp/tools/get_column_lineage.py +1988 -0
- dbt_core_mcp/tools/get_lineage.py +89 -0
- dbt_core_mcp/tools/get_project_info.py +96 -0
- dbt_core_mcp/tools/get_resource_info.py +134 -0
- dbt_core_mcp/tools/install_deps.py +102 -0
- dbt_core_mcp/tools/list_resources.py +84 -0
- dbt_core_mcp/tools/load_seeds.py +179 -0
- dbt_core_mcp/tools/query_database.py +459 -0
- dbt_core_mcp/tools/run_models.py +234 -0
- dbt_core_mcp/tools/snapshot_models.py +120 -0
- dbt_core_mcp/tools/test_models.py +238 -0
- dbt_core_mcp/utils/__init__.py +1 -0
- dbt_core_mcp/utils/env_detector.py +186 -0
- dbt_core_mcp/utils/process_check.py +130 -0
- dbt_core_mcp/utils/tool_utils.py +411 -0
- dbt_core_mcp/utils/warehouse_adapter.py +82 -0
- dbt_core_mcp/utils/warehouse_databricks.py +297 -0
- iflow_mcp_niclasolofsson_dbt_core_mcp-1.7.0.dist-info/METADATA +784 -0
- iflow_mcp_niclasolofsson_dbt_core_mcp-1.7.0.dist-info/RECORD +38 -0
- iflow_mcp_niclasolofsson_dbt_core_mcp-1.7.0.dist-info/WHEEL +4 -0
- iflow_mcp_niclasolofsson_dbt_core_mcp-1.7.0.dist-info/entry_points.txt +2 -0
- iflow_mcp_niclasolofsson_dbt_core_mcp-1.7.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
"""Run dbt build (execute models and tests together in dependency order).
|
|
2
|
+
|
|
3
|
+
This module implements the build_models tool for dbt Core MCP.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import logging
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
from fastmcp.dependencies import Depends # type: ignore[reportAttributeAccessIssue]
|
|
10
|
+
from fastmcp.server.context import Context
|
|
11
|
+
|
|
12
|
+
from ..context import DbtCoreServerContext
|
|
13
|
+
from ..dependencies import get_state
|
|
14
|
+
from . import dbtTool
|
|
15
|
+
|
|
16
|
+
logger = logging.getLogger(__name__)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
async def _implementation(
|
|
20
|
+
ctx: Context | None,
|
|
21
|
+
select: str | None,
|
|
22
|
+
exclude: str | None,
|
|
23
|
+
select_state_modified: bool,
|
|
24
|
+
select_state_modified_plus_downstream: bool,
|
|
25
|
+
full_refresh: bool,
|
|
26
|
+
resource_types: list[str] | None,
|
|
27
|
+
fail_fast: bool,
|
|
28
|
+
state: DbtCoreServerContext,
|
|
29
|
+
) -> dict[str, Any]:
|
|
30
|
+
"""Implementation function for build_models tool.
|
|
31
|
+
|
|
32
|
+
Separated for testing purposes - tests call this directly with explicit state.
|
|
33
|
+
The @tool() decorated build_models() function calls this with injected dependencies.
|
|
34
|
+
"""
|
|
35
|
+
# Ensure dbt components are initialized
|
|
36
|
+
await state.ensure_initialized(ctx, force_parse=False)
|
|
37
|
+
|
|
38
|
+
# Build selector (state-based preferred when available)
|
|
39
|
+
selector = await state.prepare_state_based_selection(select_state_modified, select_state_modified_plus_downstream, select)
|
|
40
|
+
|
|
41
|
+
if select_state_modified and not selector:
|
|
42
|
+
raise RuntimeError("No previous state found - cannot determine modifications. Run 'dbt build' first to create baseline state.")
|
|
43
|
+
|
|
44
|
+
# Construct dbt CLI args for build
|
|
45
|
+
args = ["build"]
|
|
46
|
+
|
|
47
|
+
cache_selected_only = True
|
|
48
|
+
if cache_selected_only and (select or selector or select_state_modified):
|
|
49
|
+
args.append("--cache-selected-only")
|
|
50
|
+
|
|
51
|
+
if selector:
|
|
52
|
+
target_path = state.get_project_paths().get("target-path", "target")
|
|
53
|
+
args.extend(["-s", selector, "--state", f"{target_path}/state_last_run"])
|
|
54
|
+
elif select:
|
|
55
|
+
args.extend(["-s", select])
|
|
56
|
+
|
|
57
|
+
if exclude:
|
|
58
|
+
args.extend(["--exclude", exclude])
|
|
59
|
+
|
|
60
|
+
if resource_types:
|
|
61
|
+
for rt in resource_types:
|
|
62
|
+
args.extend(["--resource-type", rt])
|
|
63
|
+
|
|
64
|
+
if full_refresh:
|
|
65
|
+
args.append("--full-refresh")
|
|
66
|
+
|
|
67
|
+
if fail_fast:
|
|
68
|
+
args.append("--fail-fast")
|
|
69
|
+
|
|
70
|
+
logger.info(f"Running DBT build with args: {args}")
|
|
71
|
+
|
|
72
|
+
# Stream progress back to MCP client (if provided)
|
|
73
|
+
async def progress_callback(current: int, total: int, message: str) -> None:
|
|
74
|
+
if ctx:
|
|
75
|
+
await ctx.report_progress(progress=current, total=total, message=message)
|
|
76
|
+
|
|
77
|
+
# Clear stale run_results so we parse only fresh output
|
|
78
|
+
state.clear_stale_run_results()
|
|
79
|
+
|
|
80
|
+
runner = await state.get_runner()
|
|
81
|
+
result = await runner.invoke(args, progress_callback=progress_callback if ctx else None) # type: ignore
|
|
82
|
+
|
|
83
|
+
run_results = state.validate_and_parse_results(result, "build")
|
|
84
|
+
|
|
85
|
+
if result and result.success:
|
|
86
|
+
await state.save_execution_state()
|
|
87
|
+
|
|
88
|
+
results_list = run_results.get("results", [])
|
|
89
|
+
if ctx:
|
|
90
|
+
if results_list:
|
|
91
|
+
passed_count = sum(1 for r in results_list if r.get("status") in ("success", "pass"))
|
|
92
|
+
failed_count = sum(1 for r in results_list if r.get("status") in ("error", "fail"))
|
|
93
|
+
skip_count = sum(1 for r in results_list if r.get("status") == "skipped")
|
|
94
|
+
|
|
95
|
+
total = len(results_list)
|
|
96
|
+
parts = []
|
|
97
|
+
if passed_count > 0:
|
|
98
|
+
parts.append(f"✅ {passed_count} passed" if failed_count > 0 or skip_count > 0 else "✅ All passed")
|
|
99
|
+
if failed_count > 0:
|
|
100
|
+
parts.append(f"❌ {failed_count} failed")
|
|
101
|
+
if skip_count > 0:
|
|
102
|
+
parts.append(f"⏭️ {skip_count} skipped")
|
|
103
|
+
|
|
104
|
+
summary = f"Build: {total}/{total} resources completed ({', '.join(parts)})"
|
|
105
|
+
await ctx.report_progress(progress=total, total=total, message=summary)
|
|
106
|
+
else:
|
|
107
|
+
await ctx.report_progress(progress=0, total=0, message="0 resources matched selector")
|
|
108
|
+
|
|
109
|
+
if not results_list:
|
|
110
|
+
raise RuntimeError(f"No resources matched selector: {select or selector or 'all'}")
|
|
111
|
+
|
|
112
|
+
return {
|
|
113
|
+
"status": "success",
|
|
114
|
+
"command": " ".join(args),
|
|
115
|
+
"results": run_results.get("results", []),
|
|
116
|
+
"elapsed_time": run_results.get("elapsed_time"),
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
@dbtTool()
|
|
121
|
+
async def build_models(
|
|
122
|
+
ctx: Context,
|
|
123
|
+
select: str | None = None,
|
|
124
|
+
exclude: str | None = None,
|
|
125
|
+
select_state_modified: bool = False,
|
|
126
|
+
select_state_modified_plus_downstream: bool = False,
|
|
127
|
+
full_refresh: bool = False,
|
|
128
|
+
resource_types: list[str] | None = None,
|
|
129
|
+
fail_fast: bool = False,
|
|
130
|
+
state: DbtCoreServerContext = Depends(get_state),
|
|
131
|
+
) -> dict[str, Any]:
|
|
132
|
+
"""Run dbt build (execute models and tests together in correct dependency order).
|
|
133
|
+
|
|
134
|
+
**When to use**: This is the recommended "do everything" command that runs seeds, models,
|
|
135
|
+
snapshots, and tests in the correct order based on your DAG. It automatically handles
|
|
136
|
+
dependencies, so you don't need to run load_seeds() → run_models() → test_models() separately.
|
|
137
|
+
|
|
138
|
+
**How it works**: Executes resources in dependency order:
|
|
139
|
+
1. Seeds (if selected)
|
|
140
|
+
2. Models (with their upstream dependencies)
|
|
141
|
+
3. Tests (after their parent models complete)
|
|
142
|
+
4. Snapshots (if selected)
|
|
143
|
+
|
|
144
|
+
State-based selection modes (uses dbt state:modified selector):
|
|
145
|
+
- select_state_modified: Build only resources modified since last successful run (state:modified)
|
|
146
|
+
- select_state_modified_plus_downstream: Build modified + downstream dependencies (state:modified+)
|
|
147
|
+
Note: Requires select_state_modified=True
|
|
148
|
+
|
|
149
|
+
Manual selection (alternative to state-based):
|
|
150
|
+
- select: dbt selector syntax (e.g., "customers", "tag:mart", "stg_*")
|
|
151
|
+
- exclude: Exclude specific models
|
|
152
|
+
|
|
153
|
+
Args:
|
|
154
|
+
select: Manual selector
|
|
155
|
+
exclude: Exclude selector
|
|
156
|
+
select_state_modified: Use state:modified selector (changed resources only)
|
|
157
|
+
select_state_modified_plus_downstream: Extend to state:modified+ (changed + downstream)
|
|
158
|
+
full_refresh: Force full refresh of incremental models
|
|
159
|
+
resource_types: Filter by resource types (model, test, seed, snapshot)
|
|
160
|
+
fail_fast: Stop execution on first failure
|
|
161
|
+
state: Shared state object injected by FastMCP
|
|
162
|
+
|
|
163
|
+
Returns:
|
|
164
|
+
Build results with status, models run/tested, and timing info
|
|
165
|
+
|
|
166
|
+
See also:
|
|
167
|
+
- run_models(): Run only models (no tests)
|
|
168
|
+
- test_models(): Run only tests
|
|
169
|
+
- load_seeds(): Run only seeds
|
|
170
|
+
|
|
171
|
+
Examples:
|
|
172
|
+
# Full project build (first-time setup or comprehensive run)
|
|
173
|
+
build_models()
|
|
174
|
+
|
|
175
|
+
# Build only what changed (efficient incremental workflow)
|
|
176
|
+
build_models(select_state_modified=True)
|
|
177
|
+
|
|
178
|
+
# Build changed resources + everything downstream
|
|
179
|
+
build_models(select_state_modified=True, select_state_modified_plus_downstream=True)
|
|
180
|
+
|
|
181
|
+
# Build specific model and its dependencies + tests
|
|
182
|
+
build_models(select="customers")
|
|
183
|
+
|
|
184
|
+
# Build all marts (includes their seed dependencies automatically)
|
|
185
|
+
build_models(select="tag:mart")
|
|
186
|
+
|
|
187
|
+
# Quick feedback: stop on first test failure
|
|
188
|
+
build_models(fail_fast=True)
|
|
189
|
+
"""
|
|
190
|
+
return await _implementation(ctx, select, exclude, select_state_modified, select_state_modified_plus_downstream, full_refresh, resource_types, fail_fast, state)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Demo tools for MCP UI experiments."""
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8" />
|
|
5
|
+
<script>
|
|
6
|
+
// ===== STATE =====
|
|
7
|
+
const state = {
|
|
8
|
+
nextId: 1,
|
|
9
|
+
hostContext: null,
|
|
10
|
+
toolInput: null,
|
|
11
|
+
lastSize: { width: 0, height: 0 }
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
// ===== LOGGING =====
|
|
15
|
+
const Logger = {
|
|
16
|
+
types: {
|
|
17
|
+
out: { class: 'log-out', arrow: '→', label: 'TO HOST' },
|
|
18
|
+
in: { class: 'log-in', arrow: '←', label: 'FROM HOST' },
|
|
19
|
+
system: { class: 'log-system', arrow: 'ℹ️', label: 'SYSTEM' }
|
|
20
|
+
},
|
|
21
|
+
|
|
22
|
+
log(type, message) {
|
|
23
|
+
const log = document.getElementById('log');
|
|
24
|
+
if (!log) return;
|
|
25
|
+
|
|
26
|
+
const config = this.types[type];
|
|
27
|
+
const entry = document.createElement('div');
|
|
28
|
+
entry.className = config.class;
|
|
29
|
+
entry.innerHTML = `
|
|
30
|
+
<div class="log-header">${new Date().toLocaleTimeString()} ${config.arrow} ${config.label}</div>
|
|
31
|
+
<pre>${JSON.stringify(message, null, 2)}</pre>
|
|
32
|
+
`;
|
|
33
|
+
log.appendChild(entry);
|
|
34
|
+
log.scrollTop = log.scrollHeight;
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
// ===== SIZE MANAGEMENT =====
|
|
39
|
+
const SizeManager = {
|
|
40
|
+
update() {
|
|
41
|
+
const viewportHeight = window.innerHeight;
|
|
42
|
+
const maxHeight = Math.floor(viewportHeight * 0.4);
|
|
43
|
+
const currentHeight = document.body.scrollHeight;
|
|
44
|
+
|
|
45
|
+
document.getElementById('viewport-height').textContent = viewportHeight;
|
|
46
|
+
document.getElementById('max-height').textContent = maxHeight;
|
|
47
|
+
document.getElementById('current-size').textContent = currentHeight;
|
|
48
|
+
},
|
|
49
|
+
|
|
50
|
+
notify() {
|
|
51
|
+
const width = document.body.scrollWidth;
|
|
52
|
+
const height = document.body.scrollHeight;
|
|
53
|
+
|
|
54
|
+
this.update();
|
|
55
|
+
|
|
56
|
+
if (width !== state.lastSize.width || height !== state.lastSize.height) {
|
|
57
|
+
state.lastSize = { width, height };
|
|
58
|
+
window.parent.postMessage({
|
|
59
|
+
jsonrpc: "2.0",
|
|
60
|
+
method: "ui/notifications/size-changed",
|
|
61
|
+
params: { width, height }
|
|
62
|
+
}, '*');
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
// ===== MESSAGING =====
|
|
68
|
+
const Messenger = {
|
|
69
|
+
request(method, params) {
|
|
70
|
+
const id = state.nextId++;
|
|
71
|
+
const message = { jsonrpc: "2.0", id, method, params };
|
|
72
|
+
Logger.log('out', message);
|
|
73
|
+
window.parent.postMessage(message, '*');
|
|
74
|
+
return id;
|
|
75
|
+
},
|
|
76
|
+
|
|
77
|
+
notification(method, params) {
|
|
78
|
+
const message = { jsonrpc: "2.0", method, params };
|
|
79
|
+
Logger.log('out', message);
|
|
80
|
+
window.parent.postMessage(message, '*');
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
// ===== MESSAGE HANDLERS =====
|
|
85
|
+
function handleResponse(message) {
|
|
86
|
+
if (!message.result?.hostContext) return;
|
|
87
|
+
|
|
88
|
+
state.hostContext = message.result.hostContext;
|
|
89
|
+
|
|
90
|
+
// Send initialized notification
|
|
91
|
+
Messenger.notification("ui/notifications/initialized", {});
|
|
92
|
+
|
|
93
|
+
// Apply theme variables from host
|
|
94
|
+
if (state.hostContext.styles?.variables) {
|
|
95
|
+
const root = document.documentElement;
|
|
96
|
+
Object.entries(state.hostContext.styles.variables).forEach(([key, value]) => {
|
|
97
|
+
root.style.setProperty(key, value);
|
|
98
|
+
});
|
|
99
|
+
console.log(`✅ Applied ${Object.keys(state.hostContext.styles.variables).length} theme variables`);
|
|
100
|
+
|
|
101
|
+
// Check if variables resolved properly before showing content
|
|
102
|
+
const testColor = getComputedStyle(root).getPropertyValue('--color-text-primary').trim();
|
|
103
|
+
if (testColor && testColor !== 'magenta') {
|
|
104
|
+
document.body.style.opacity = '1';
|
|
105
|
+
console.log('✅ Variables resolved, showing content');
|
|
106
|
+
} else {
|
|
107
|
+
// Fallback: show anyway after short delay
|
|
108
|
+
setTimeout(() => {
|
|
109
|
+
document.body.style.opacity = '1';
|
|
110
|
+
console.log('⚠️ Showing content with fallback colors');
|
|
111
|
+
}, 100);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function handleNotification(message) {
|
|
117
|
+
if (message.method === 'ui/notifications/tool-input') {
|
|
118
|
+
state.toolInput = message.params.arguments;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (message.method === 'ui/notifications/shutdown') {
|
|
122
|
+
Logger.log('system', {
|
|
123
|
+
type: 'system',
|
|
124
|
+
message: 'Received shutdown notification'
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// ===== INITIALIZATION =====
|
|
130
|
+
function initialize() {
|
|
131
|
+
console.log('🔄 Initialize called');
|
|
132
|
+
SizeManager.update();
|
|
133
|
+
|
|
134
|
+
Logger.log('system', {
|
|
135
|
+
type: 'system',
|
|
136
|
+
message: 'MCP App starting initialization...'
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
Messenger.request("ui/initialize", {
|
|
140
|
+
capabilities: {},
|
|
141
|
+
clientInfo: { name: "dbt-demo-ui", version: "1.0.0" },
|
|
142
|
+
protocolVersion: "2025-06-18"
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
// Size observer
|
|
146
|
+
new ResizeObserver(() => SizeManager.notify()).observe(document.body);
|
|
147
|
+
window.addEventListener('resize', SizeManager.update);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// ===== EVENT LISTENERS =====
|
|
151
|
+
window.addEventListener('message', event => {
|
|
152
|
+
const message = event.data;
|
|
153
|
+
Logger.log('in', message);
|
|
154
|
+
|
|
155
|
+
if (message.jsonrpc === "2.0" && message.id) {
|
|
156
|
+
handleResponse(message);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (message.jsonrpc === "2.0" && message.method) {
|
|
160
|
+
handleNotification(message);
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
document.addEventListener('DOMContentLoaded', initialize);
|
|
165
|
+
</script>
|
|
166
|
+
<style>
|
|
167
|
+
* { box-sizing: border-box; }
|
|
168
|
+
|
|
169
|
+
html, body { margin: 0; padding: 0; height: auto; }
|
|
170
|
+
|
|
171
|
+
body {
|
|
172
|
+
padding: 0;
|
|
173
|
+
font-family: var(--font-sans, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif);
|
|
174
|
+
font-size: var(--font-text-md-size, 13px);
|
|
175
|
+
color: var(--color-text-primary, magenta);
|
|
176
|
+
background: transparent;
|
|
177
|
+
opacity: 0;
|
|
178
|
+
transition: opacity 0.15s ease-in;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
h1 {
|
|
182
|
+
margin: 0 0 8px;
|
|
183
|
+
font-size: var(--font-heading-md-size, 20px);
|
|
184
|
+
font-weight: var(--font-weight-semibold, 600);
|
|
185
|
+
color: var(--color-text-primary, magenta);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
p {
|
|
189
|
+
margin: 0 0 16px;
|
|
190
|
+
color: var(--color-text-secondary, yellow);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
#size-info {
|
|
194
|
+
background: var(--color-background-secondary, #ff1493);
|
|
195
|
+
border: var(--border-width-regular, 1px) solid var(--color-border-primary, red);
|
|
196
|
+
border-radius: var(--border-radius-lg, 6px);
|
|
197
|
+
padding: 12px;
|
|
198
|
+
margin-bottom: 16px;
|
|
199
|
+
font-family: var(--font-mono, monospace);
|
|
200
|
+
font-size: var(--font-text-sm-size, 11px);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
#size-info div { margin: 4px 0; }
|
|
204
|
+
#size-info strong { color: var(--color-text-primary, magenta); }
|
|
205
|
+
#size-info span { color: var(--color-text-success, lime); }
|
|
206
|
+
|
|
207
|
+
#log {
|
|
208
|
+
background: var(--color-background-secondary, #ff1493);
|
|
209
|
+
border: var(--border-width-regular, 1px) solid var(--color-border-primary, red);
|
|
210
|
+
border-radius: var(--border-radius-lg, 6px);
|
|
211
|
+
padding: 12px;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
.log-out, .log-in, .log-system {
|
|
215
|
+
margin-bottom: 12px;
|
|
216
|
+
padding: 8px;
|
|
217
|
+
border-radius: var(--border-radius-md, 4px);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
.log-out {
|
|
221
|
+
background: var(--color-background-info, #8b00ff);
|
|
222
|
+
border-left: 3px solid var(--color-border-info, cyan);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
.log-in {
|
|
226
|
+
background: rgba(115, 201, 145, 0.1);
|
|
227
|
+
border-left: 3px solid var(--color-border-success, lime);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
.log-system {
|
|
231
|
+
background: rgba(128, 128, 128, 0.1);
|
|
232
|
+
border-left: 3px solid var(--color-border-primary, red);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
.log-header {
|
|
236
|
+
font-size: var(--font-text-xs-size, 10px);
|
|
237
|
+
font-weight: var(--font-weight-semibold, 600);
|
|
238
|
+
margin-bottom: 4px;
|
|
239
|
+
color: var(--color-text-secondary, yellow);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
.log-out .log-header { color: var(--color-text-primary, magenta); }
|
|
243
|
+
.log-in .log-header { color: var(--color-text-success, lime); }
|
|
244
|
+
|
|
245
|
+
pre {
|
|
246
|
+
margin: 0;
|
|
247
|
+
font-family: var(--font-mono, 'Consolas', 'Monaco', monospace);
|
|
248
|
+
font-size: var(--font-text-xs-size, 10px);
|
|
249
|
+
color: var(--color-text-primary, magenta);
|
|
250
|
+
white-space: pre-wrap;
|
|
251
|
+
word-wrap: break-word;
|
|
252
|
+
}
|
|
253
|
+
</style>
|
|
254
|
+
</head>
|
|
255
|
+
<body>
|
|
256
|
+
<h1>MCP Apps Protocol Monitor 📡</h1>
|
|
257
|
+
<p>Self-logging JSON-RPC message viewer</p>
|
|
258
|
+
|
|
259
|
+
<div id="size-info">
|
|
260
|
+
<div><strong>Viewport Height:</strong> <span id="viewport-height">-</span>px</div>
|
|
261
|
+
<div><strong>40% Max (40vh):</strong> <span id="max-height">-</span>px</div>
|
|
262
|
+
<div><strong>Current Body Size:</strong> <span id="current-size">-</span>px</div>
|
|
263
|
+
</div>
|
|
264
|
+
|
|
265
|
+
<div id="log"></div>
|
|
266
|
+
</body>
|
|
267
|
+
</html>
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""Simple tool for MCP UI resource testing."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from fastmcp.server.context import Context
|
|
7
|
+
|
|
8
|
+
from .. import dbtTool
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger(__name__)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dbtTool(
|
|
14
|
+
description="Render the demo UI resource (resource://demo/hello).",
|
|
15
|
+
meta={
|
|
16
|
+
"ui": {
|
|
17
|
+
"resourceUri": "resource://demo/hello",
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
)
|
|
21
|
+
async def demo_ui(_ctx: Context) -> dict[str, Any]:
|
|
22
|
+
"""Render the demo UI resource (resource://demo/hello)."""
|
|
23
|
+
logger.info("demo_ui tool called")
|
|
24
|
+
return {
|
|
25
|
+
"message": "Demo UI tool invoked",
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
# @dbtTool(description="Return basic client context metadata for diagnostics.")
|
|
30
|
+
# async def demo_client_context(ctx: Context) -> dict[str, Any]:
|
|
31
|
+
# """Return basic client context metadata for diagnostics."""
|
|
32
|
+
# logger.info("demo_client_context tool called")
|
|
33
|
+
# meta = getattr(getattr(ctx, "request_context", None), "meta", None)
|
|
34
|
+
# meta_value = meta if meta is None else getattr(meta, "__dict__", meta)
|
|
35
|
+
# return {
|
|
36
|
+
# "client_id": getattr(ctx, "client_id", None),
|
|
37
|
+
# "request_id": getattr(ctx, "request_id", None),
|
|
38
|
+
# "session_id": getattr(ctx, "session_id", None),
|
|
39
|
+
# "has_sampling_capability": getattr(ctx, "has_sampling_capability", None),
|
|
40
|
+
# "request_meta": meta_value,
|
|
41
|
+
# }
|