Skip to content

Commit 415e776

Browse files
authored
feat: add resource templates, progress notifications, and client logging (#111)
Implements Phases 3 and 4 of the MCP protocol capabilities expansion (#102). Resource templates expose platform data as browseable RFC 6570 URI resources. Progress notifications provide real-time feedback during long-running Trino queries. Client logging sends enrichment decisions to MCP clients that opt in via setLevel. Phase 3 — Resource Templates: - schema://{catalog}.{schema}/{table} — merged query + semantic schema - glossary://{term} — glossary term definitions from semantic provider - availability://{catalog}.{schema}/{table} — query availability info - URI variable parsing via uritemplate/v3 library - All templates gated by resources.enabled config Phase 4 — Progress Notifications & Client Logging: - MCPProgressNotifier adapter bridges ServerSession.NotifyProgress() to mcp-trino ProgressNotifier interface via ToolMiddleware injection - Three granular progress points in query handler (0/3, 1/3, 2/3) - ServerSession + progress token context propagation in MCP middleware - Client logging middleware emits enrichment info after semantic enrichment runs, silent no-op when client hasn't called setLevel - New pkg/mcpcontext package breaks import cycle between middleware and toolkits/trino for shared context keys Dependency: mcp-trino v0.6.1 (ProgressNotifier interface)
1 parent ad98274 commit 415e776

22 files changed

+1800
-34
lines changed

README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,15 @@ Every tool call is logged with user identity, persona, request details, and timi
113113
### Knowledge Capture
114114
AI sessions generate valuable domain knowledge: column meanings, data quality issues, business rules. The `capture_insight` tool records these observations during sessions, and `apply_knowledge` provides admins with a structured review workflow. Approved insights are written back to DataHub with full changeset tracking and rollback. An [Admin REST API](https://txn2.github.io/mcp-data-platform/knowledge/admin-api/) supports integration with existing governance tools. See the [Knowledge Capture documentation](https://txn2.github.io/mcp-data-platform/knowledge/overview/) for details.
115115

116+
### Resource Templates
117+
Browse platform data as parameterized MCP resources using RFC 6570 URI templates. Three built-in templates expose table schemas (`schema://catalog.schema/table`), glossary terms (`glossary://term`), and data availability (`availability://catalog.schema/table`) without making tool calls.
118+
119+
### Progress Notifications
120+
Long-running Trino queries send granular progress updates to MCP clients (executing, formatting, complete). Clients that provide a `_meta.progressToken` receive real-time status. Zero overhead when disabled.
121+
122+
### Client Logging
123+
Server-to-client log messages give AI agents visibility into platform decisions (enrichment applied, timing). Uses the MCP `logging/setLevel` protocol — zero overhead if the client hasn't opted in.
124+
116125
### Extensible Middleware Architecture
117126
Add custom authentication, rate limiting, or logging. Swap providers to integrate different semantic layers or query engines. The Go library exposes everything—build the platform your organization needs.
118127

@@ -367,6 +376,7 @@ database:
367376
| `pkg/semantic` | Semantic metadata provider abstraction |
368377
| `pkg/query` | Query execution provider abstraction |
369378
| `pkg/middleware` | Request/response middleware chain |
379+
| `pkg/mcpcontext` | MCP session/progress context helpers |
370380
| `pkg/registry` | Toolkit registration and management |
371381
| `pkg/audit` | Audit logging with PostgreSQL storage |
372382
| `pkg/tuning` | Prompts, hints, and operational rules |

configs/platform.yaml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,26 @@ tuning:
280280
# persona: admin # persona required for admin access (default: "admin")
281281
# path_prefix: /api/v1/admin # URL prefix for admin endpoints
282282

283+
# Resource templates (RFC 6570 URI templates)
284+
# Exposes platform data as browseable, parameterized MCP resources.
285+
# Templates: schema://{catalog}.{schema}/{table}, glossary://{term},
286+
# availability://{catalog}.{schema}/{table}
287+
# resources:
288+
# enabled: true
289+
290+
# Progress notifications
291+
# Sends granular progress updates to MCP clients during long-running
292+
# Trino queries (requires client to send _meta.progressToken).
293+
# progress:
294+
# enabled: true
295+
296+
# Client logging
297+
# Sends server-to-client log messages (enrichment decisions, timing)
298+
# via MCP logging/setLevel protocol. Zero overhead if client hasn't
299+
# called setLevel.
300+
# client_logging:
301+
# enabled: true
302+
283303
# Audit logging
284304
audit:
285305
enabled: false

docs/llms-full.txt

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,12 @@ The only requirement is DataHub (https://datahubproject.io/). Add Trino (https:/
2626

2727
- **Personas**: Define who can use which tools. Analysts get read access. Admins get everything. Map from your identity provider's roles.
2828

29+
- **Resource Templates**: Browse platform data as parameterized MCP resources using RFC 6570 URI templates. Three built-in templates: table schemas (`schema://catalog.schema/table`), glossary terms (`glossary://term`), and data availability (`availability://catalog.schema/table`).
30+
31+
- **Progress Notifications**: Long-running Trino queries send granular progress updates (executing, formatting, complete) to clients that provide `_meta.progressToken`. Zero overhead when disabled.
32+
33+
- **Client Logging**: Server-to-client log messages for platform decisions (enrichment, timing) via MCP `logging/setLevel` protocol. Zero overhead if the client hasn't opted in.
34+
2935
---
3036

3137
# The Data Stack: DataHub + Trino + S3
@@ -288,6 +294,33 @@ No patterns configured means all tools are visible. When both are set, allow is
288294

289295
When `admin.portal: true`, an interactive web dashboard is served at the admin path prefix. It provides audit log exploration, tool execution testing, and system monitoring.
290296

297+
## Resource Templates Configuration
298+
299+
| Field | Type | Default | Description |
300+
|-------|------|---------|-------------|
301+
| `resources.enabled` | bool | `false` | Enable MCP resource templates |
302+
303+
When enabled, three RFC 6570 URI templates are registered:
304+
- `schema://{catalog}.{schema_name}/{table}` — Table schema with semantic context
305+
- `glossary://{term}` — Glossary term definition and related assets
306+
- `availability://{catalog}.{schema_name}/{table}` — Data availability status and row count
307+
308+
## Progress Notifications Configuration
309+
310+
| Field | Type | Default | Description |
311+
|-------|------|---------|-------------|
312+
| `progress.enabled` | bool | `false` | Enable progress notifications for Trino queries |
313+
314+
When enabled, Trino query tools send three progress notifications per query: before execution, after query returns, and after formatting. Clients must include `_meta.progressToken` in their tool call to receive notifications.
315+
316+
## Client Logging Configuration
317+
318+
| Field | Type | Default | Description |
319+
|-------|------|---------|-------------|
320+
| `client_logging.enabled` | bool | `false` | Enable server-to-client log messages |
321+
322+
When enabled, the platform sends log notifications to clients after enrichment is applied (tool name, duration). Uses MCP `logging/setLevel` protocol — zero overhead if the client hasn't called `setLevel`.
323+
291324
## Audit Configuration
292325

293326
| Field | Type | Default | Description |
@@ -1001,6 +1034,7 @@ Use the library when you need to:
10011034
| `pkg/semantic` | Semantic provider interface |
10021035
| `pkg/query` | Query provider interface |
10031036
| `pkg/middleware` | Request/response processing |
1037+
| `pkg/mcpcontext` | MCP session/progress context helpers |
10041038
| `pkg/persona` | Role-based tool filtering |
10051039
| `pkg/auth` | OIDC and API key validation |
10061040
| `pkg/admin` | Admin REST API for knowledge management |

docs/llms.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
- [Home](index.md): Introduction, quick start, and key features
1212
- [Server Overview](server/overview.md): What the platform does, architecture, request flow
1313
- [Installation](server/installation.md): Install via go install, Homebrew, Docker, or from source
14-
- [Configuration](server/configuration.md): YAML configuration with environment variable expansion, config versioning (apiVersion field, version lifecycle, migrate-config CLI), config store options (file vs database mode), tool visibility filtering (allow/deny patterns for tools/list token reduction), admin API and portal, database, audit, and session configuration
14+
- [Configuration](server/configuration.md): YAML configuration with environment variable expansion, config versioning (apiVersion field, version lifecycle, migrate-config CLI), config store options (file vs database mode), tool visibility filtering (allow/deny patterns for tools/list token reduction), resource templates, progress notifications, client logging, admin API and portal, database, audit, and session configuration
1515
- [Operating Modes](server/operating-modes.md): Three deployment modes — standalone (no database), full-config file + database, bootstrap + database config. Feature availability by mode, example configurations, decision guide
1616
- [Admin Portal](server/admin-portal.md): Built-in web dashboard for monitoring and managing the platform. Dashboard with activity timelines, performance percentiles, error monitoring. Tools overview with connection grid and tool inventory. Interactive tool explorer with semantic enrichment display. Searchable audit log with event detail drawer. Knowledge insight governance with approve/reject workflow and changeset tracking. Local dev with MSW mock data
1717
- [Admin API](server/admin-api.md): REST endpoints for system info, config management, personas, auth keys, audit, knowledge. Authentication, operating mode behavior, request/response reference. Interactive Swagger UI at `/api/v1/admin/docs/`
@@ -73,7 +73,7 @@
7373
- [Tools API](reference/tools-api.md): Complete tool specifications with parameters and responses
7474
- [Configuration](reference/configuration.md): Full YAML schema with all options
7575
- [Providers](reference/providers.md): Semantic, query, and storage provider interfaces
76-
- [Middleware](reference/middleware.md): Request processing chain including tool visibility middleware
76+
- [Middleware](reference/middleware.md): Request processing chain including tool visibility middleware, client logging, progress notifications
7777

7878
## Support
7979

go.mod

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ require (
1616
github.com/testcontainers/testcontainers-go/modules/postgres v0.40.0
1717
github.com/txn2/mcp-datahub v0.7.0
1818
github.com/txn2/mcp-s3 v0.2.0
19-
github.com/txn2/mcp-trino v0.5.0
19+
github.com/txn2/mcp-trino v0.6.1
20+
github.com/yosida95/uritemplate/v3 v3.0.2
2021
golang.org/x/crypto v0.48.0
2122
gopkg.in/yaml.v3 v3.0.1
2223
)
@@ -102,7 +103,6 @@ require (
102103
github.com/tklauser/go-sysconf v0.3.12 // indirect
103104
github.com/tklauser/numcpus v0.6.1 // indirect
104105
github.com/trinodb/trino-go-client v0.333.0 // indirect
105-
github.com/yosida95/uritemplate/v3 v3.0.2 // indirect
106106
github.com/yusufpapurcu/wmi v1.2.4 // indirect
107107
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
108108
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -268,8 +268,8 @@ github.com/txn2/mcp-datahub v0.7.0 h1:gzBGJmYzjy7GBB0HGXm4o6pZdvVrPrhVbNEC3c5fpX
268268
github.com/txn2/mcp-datahub v0.7.0/go.mod h1:UyXoTLT9H5DFkrdi/k0G82x+fZN5N4PSjom6FPGTkI0=
269269
github.com/txn2/mcp-s3 v0.2.0 h1:hycUsa8j6hz1rHDIr0OOe6wbDxW/aJgrCYTvqrAcgSM=
270270
github.com/txn2/mcp-s3 v0.2.0/go.mod h1:ygY1Bz6aXO4/3jvhfooR6y/BTXJ35Rsvwsl75zKktXg=
271-
github.com/txn2/mcp-trino v0.5.0 h1:xxJ8kDVNbNVvWajQYuID96ywbppcRMhO+f/VxNpODwI=
272-
github.com/txn2/mcp-trino v0.5.0/go.mod h1:6m5jlHTExGTr2swFRFp2McDBHti8l/F7mHi2b1cYHk4=
271+
github.com/txn2/mcp-trino v0.6.1 h1:IZnBlyccHXftIfiKZciltRvQ/JtOPEz8Bxa4CxfwB7A=
272+
github.com/txn2/mcp-trino v0.6.1/go.mod h1:6m5jlHTExGTr2swFRFp2McDBHti8l/F7mHi2b1cYHk4=
273273
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo=
274274
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
275275
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=

pkg/mcpcontext/mcpcontext.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// Package mcpcontext provides context helpers for MCP session state.
2+
// These are in a separate package to avoid import cycles between
3+
// middleware and toolkit packages.
4+
package mcpcontext
5+
6+
import (
7+
"context"
8+
9+
"github.com/modelcontextprotocol/go-sdk/mcp"
10+
)
11+
12+
// contextKey is a private type for context keys.
13+
type contextKey int
14+
15+
const (
16+
serverSessionKey contextKey = iota
17+
progressTokenKey
18+
)
19+
20+
// WithServerSession adds a ServerSession to the context.
21+
func WithServerSession(ctx context.Context, ss *mcp.ServerSession) context.Context {
22+
return context.WithValue(ctx, serverSessionKey, ss)
23+
}
24+
25+
// GetServerSession retrieves the ServerSession from the context.
26+
func GetServerSession(ctx context.Context) *mcp.ServerSession {
27+
ss, _ := ctx.Value(serverSessionKey).(*mcp.ServerSession)
28+
return ss
29+
}
30+
31+
// WithProgressToken adds a progress token to the context.
32+
func WithProgressToken(ctx context.Context, token any) context.Context {
33+
return context.WithValue(ctx, progressTokenKey, token)
34+
}
35+
36+
// GetProgressToken retrieves the progress token from the context.
37+
func GetProgressToken(ctx context.Context) any {
38+
return ctx.Value(progressTokenKey)
39+
}

pkg/mcpcontext/mcpcontext_test.go

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package mcpcontext
2+
3+
import (
4+
"context"
5+
"testing"
6+
7+
"github.com/modelcontextprotocol/go-sdk/mcp"
8+
)
9+
10+
func TestServerSessionContext(t *testing.T) {
11+
t.Run("not set returns nil", func(t *testing.T) {
12+
got := GetServerSession(context.Background())
13+
if got != nil {
14+
t.Error("expected nil for empty context")
15+
}
16+
})
17+
18+
t.Run("nil session stored", func(t *testing.T) {
19+
ctx := WithServerSession(context.Background(), (*mcp.ServerSession)(nil))
20+
got := GetServerSession(ctx)
21+
if got != nil {
22+
t.Error("expected nil for nil *ServerSession stored in context")
23+
}
24+
})
25+
}
26+
27+
func TestProgressTokenContext(t *testing.T) {
28+
t.Run("round-trip string token", func(t *testing.T) {
29+
ctx := WithProgressToken(context.Background(), "tok-123")
30+
got := GetProgressToken(ctx)
31+
if got != "tok-123" {
32+
t.Errorf("GetProgressToken() = %v, want %q", got, "tok-123")
33+
}
34+
})
35+
36+
t.Run("round-trip int token", func(t *testing.T) {
37+
ctx := WithProgressToken(context.Background(), 42)
38+
got := GetProgressToken(ctx)
39+
if got != 42 {
40+
t.Errorf("GetProgressToken() = %v, want %d", got, 42)
41+
}
42+
})
43+
44+
t.Run("not set returns nil", func(t *testing.T) {
45+
got := GetProgressToken(context.Background())
46+
if got != nil {
47+
t.Errorf("GetProgressToken() = %v, want nil", got)
48+
}
49+
})
50+
51+
t.Run("nil token stored", func(t *testing.T) {
52+
ctx := WithProgressToken(context.Background(), nil)
53+
got := GetProgressToken(ctx)
54+
if got != nil {
55+
t.Errorf("GetProgressToken() = %v, want nil", got)
56+
}
57+
})
58+
}

pkg/middleware/context.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@ package middleware
44
import (
55
"context"
66
"time"
7+
8+
"github.com/modelcontextprotocol/go-sdk/mcp"
9+
10+
"github.com/txn2/mcp-data-platform/pkg/mcpcontext"
711
)
812

913
// contextKey is a private type for context keys.
@@ -94,3 +98,27 @@ func GetToken(ctx context.Context) string {
9498
}
9599
return ""
96100
}
101+
102+
// WithServerSession adds a ServerSession to the context.
103+
// Delegates to mcpcontext to share context keys with toolkit packages.
104+
func WithServerSession(ctx context.Context, ss *mcp.ServerSession) context.Context {
105+
return mcpcontext.WithServerSession(ctx, ss)
106+
}
107+
108+
// GetServerSession retrieves the ServerSession from the context.
109+
// Delegates to mcpcontext to share context keys with toolkit packages.
110+
func GetServerSession(ctx context.Context) *mcp.ServerSession {
111+
return mcpcontext.GetServerSession(ctx)
112+
}
113+
114+
// WithProgressToken adds a progress token to the context.
115+
// Delegates to mcpcontext to share context keys with toolkit packages.
116+
func WithProgressToken(ctx context.Context, token any) context.Context {
117+
return mcpcontext.WithProgressToken(ctx, token)
118+
}
119+
120+
// GetProgressToken retrieves the progress token from the context.
121+
// Delegates to mcpcontext to share context keys with toolkit packages.
122+
func GetProgressToken(ctx context.Context) any {
123+
return mcpcontext.GetProgressToken(ctx)
124+
}

pkg/middleware/context_test.go

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ package middleware
33
import (
44
"context"
55
"testing"
6+
7+
"github.com/modelcontextprotocol/go-sdk/mcp"
68
)
79

810
func TestPlatformContext(t *testing.T) {
@@ -90,3 +92,56 @@ func TestTokenContext(t *testing.T) {
9092
}
9193
})
9294
}
95+
96+
func TestServerSessionContext(t *testing.T) {
97+
t.Run("round-trip", func(t *testing.T) {
98+
// We can't construct a real ServerSession (private fields), but we can
99+
// verify nil handling and type safety of the context helpers.
100+
ctx := context.Background()
101+
got := GetServerSession(ctx)
102+
if got != nil {
103+
t.Error("expected nil for empty context")
104+
}
105+
})
106+
107+
t.Run("nil session stored", func(t *testing.T) {
108+
ctx := WithServerSession(context.Background(), (*mcp.ServerSession)(nil))
109+
got := GetServerSession(ctx)
110+
if got != nil {
111+
t.Error("expected nil for nil *ServerSession stored in context")
112+
}
113+
})
114+
}
115+
116+
func TestProgressTokenContext(t *testing.T) {
117+
t.Run("round-trip string token", func(t *testing.T) {
118+
ctx := WithProgressToken(context.Background(), "tok-123")
119+
got := GetProgressToken(ctx)
120+
if got != "tok-123" {
121+
t.Errorf("GetProgressToken() = %v, want %q", got, "tok-123")
122+
}
123+
})
124+
125+
t.Run("round-trip int token", func(t *testing.T) {
126+
ctx := WithProgressToken(context.Background(), 42)
127+
got := GetProgressToken(ctx)
128+
if got != 42 {
129+
t.Errorf("GetProgressToken() = %v, want %d", got, 42)
130+
}
131+
})
132+
133+
t.Run("not set returns nil", func(t *testing.T) {
134+
got := GetProgressToken(context.Background())
135+
if got != nil {
136+
t.Errorf("GetProgressToken() = %v, want nil", got)
137+
}
138+
})
139+
140+
t.Run("nil token stored", func(t *testing.T) {
141+
ctx := WithProgressToken(context.Background(), nil)
142+
got := GetProgressToken(ctx)
143+
if got != nil {
144+
t.Errorf("GetProgressToken() = %v, want nil", got)
145+
}
146+
})
147+
}

0 commit comments

Comments
 (0)