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
7 changes: 2 additions & 5 deletions examples/everything/colin.toml
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
[project]
name = "everything"
version = "0.1.0"

[paths]
models = "models"
target = "target"
model-path = "models"
output-path = "output"

[[providers.llm]]
model = "anthropic:claude-haiku-4-5"
Expand Down
10 changes: 7 additions & 3 deletions examples/everything/models/analysis.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,17 @@ description: Extracted insights from product data
{{ colin.mcp.demo.resource('demo://greeting/Analyst') }}

## Key Pain Points
{{ ref('data') | llm_extract('List the top 3 user pain points mentioned in the feedback, as bullet points') }}

{{ ref('data.md') | llm_extract('List the top 3 user pain points mentioned in the feedback, as bullet points') }}

## Performance Summary
{{ ref('data') | llm_extract('Summarize the technical performance in one sentence') }}

{{ ref('data.md') | llm_extract('Summarize the technical performance in one sentence') }}

## Strengths
{{ ref('data') | llm_extract('What are users happy about? List as bullet points') }}

{{ ref('data.md') | llm_extract('What are users happy about? List as bullet points') }}

## Analysis Guidance

{{ colin.mcp.demo.prompt('summarize', style='detailed') }}
8 changes: 4 additions & 4 deletions examples/everything/models/executive_brief.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,19 @@ description: High-level summary for leadership
# Executive Brief: Q1 2025 Planning

## TL;DR
{{ ref('recommendations') | llm_extract('Summarize the top 3 priorities in one sentence each') }}
{{ ref('recommendations.md') | llm_extract('Summarize the top 3 priorities in one sentence each') }}

## Current State
{{ ref('goal_status') | llm_extract('Give a one-paragraph executive summary of goal progress') }}
{{ ref('goal_status.md') | llm_extract('Give a one-paragraph executive summary of goal progress') }}

## Detailed Recommendations
{{ ref('recommendations').content }}
{{ ref('recommendations.md').content }}

---

## Appendix: Raw Data
<details>
<summary>Click to expand source data</summary>

{{ ref('data').content }}
{{ ref('data.md').content }}
</details>
4 changes: 2 additions & 2 deletions examples/everything/models/goal_status.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@ description: Assessment of progress toward goals
Based on current metrics and stated objectives:

**Current Data:**
{{ ref('data').content }}
{{ ref('data.md').content }}

**Goals:**
{{ ref('goals').content }}
{{ ref('goals.md').content }}

{% llm %}
Compare the current metrics from the data above against the stated goals.
Expand Down
3 changes: 2 additions & 1 deletion examples/everything/models/metrics_api.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
name: Metrics API
description: Structured JSON output for API consumption
colin:
output: json
output:
format: json
---

## product
Expand Down
4 changes: 2 additions & 2 deletions examples/everything/models/recommendations.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ description: Prioritized action items based on analysis
# Strategic Recommendations

## Analysis Summary
{{ ref('analysis').content }}
{{ ref('analysis.md').content }}

## Goal Status
{{ ref('goal_status') | llm_extract('Which goals are at risk or behind?') }}
{{ ref('goal_status.md') | llm_extract('Which goals are at risk or behind?') }}

## Recommended Actions

Expand Down
6 changes: 3 additions & 3 deletions examples/hello_world/models/summary.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,19 @@ description: Demonstrates LLM blocks and extract filter

Here's some content to work with:

{{ ref('greeting').content }}
{{ ref('greeting.md').content }}



## Extracted Info

{{ ref('greeting') | llm_extract('the main message in one sentence') }}
{{ ref('greeting.md') | llm_extract('the main message in one sentence') }}

## LLM-Generated Content

{% llm %}
Given this greeting:
{{ ref('greeting').content }}
{{ ref('greeting.md').content }}

Write a haiku about being welcomed.
{% endllm %}
4 changes: 2 additions & 2 deletions examples/hello_world/models/welcome.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ description: Demonstrates ref() and LLM blocks

# Welcome

{{ ref('greeting').content }}
{{ ref('greeting.md').content }}

---

Expand All @@ -17,7 +17,7 @@ Colin automatically compiles documents in the right order based on their depende
{% llm %}
Translate this greeting message for French users of the Colin library:

{{ ref('greeting').content }}
{{ ref('greeting.md').content }}

The translation should feel welcoming and appropriate for a technical audience.
{% endllm %}
9 changes: 9 additions & 0 deletions examples/mcp/.colin/compiled/greeting.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# MCP Demo

## Resource Example

Hello, World! Welcome to the demo.

## Prompt Example

Summarize the following in a detailed style.
55 changes: 55 additions & 0 deletions examples/mcp/.colin/manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
{
"version": "1",
"project_name": "mcp",
"config_hash": "f6a9b770bee3be5d",
"compiled_at": "2026-01-07T21:57:28.998543Z",
"documents": {
"project://greeting.md": {
"uri": "project://greeting.md",
"source_path": null,
"source_hash": "44d23c70d58c7e85",
"output_hash": "326780bff734af74",
"output_path": "greeting.md",
"is_published": true,
"compiled_at": "2026-01-07T21:53:49.341310Z",
"refs": [
{
"provider": "mcp",
"connection": "demo",
"method": "config",
"args": {}
},
{
"provider": "mcp",
"connection": "demo",
"method": "resource",
"args": {
"uri": "demo://greeting/World"
}
},
{
"provider": "mcp",
"connection": "demo",
"method": "prompt",
"args": {
"name": "summarize",
"arguments": {
"style": "detailed"
}
}
}
],
"ref_versions": {
"{\"args\": {}, \"connection\": \"demo\", \"method\": \"config\", \"provider\": \"mcp\"}": "e0569178996485f6",
"{\"args\": {\"uri\": \"demo://greeting/World\"}, \"connection\": \"demo\", \"method\": \"resource\", \"provider\": \"mcp\"}": "6236c65af4fdeed5",
"{\"args\": {\"arguments\": {\"style\": \"detailed\"}, \"name\": \"summarize\"}, \"connection\": \"demo\", \"method\": \"prompt\", \"provider\": \"mcp\"}": "e31eb0daa2d308b8"
},
"llm_calls": {},
"total_cost_usd": 0.0,
"artifacts": [],
"sections": {},
"config_hash": "f6a9b770bee3be5d"
}
},
"cache": {}
}
18 changes: 18 additions & 0 deletions examples/mcp/colin.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
[project]
name = "mcp"
model-path = "models"
output-path = "output"

[[providers.mcp]]
name = "demo"
command = "uvx"
args = [
"--with",
"fastmcp",
"fastmcp",
"run",
"--no-banner",
"--log-level",
"ERROR",
"examples/mcp/mcp_server.py",

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Use project-relative MCP server path

The MCP command arg points to examples/mcp/mcp_server.py, but this config’s project root is examples/mcp and colin run uses the current directory as the project by default, so running the example from its own directory resolves the path to examples/mcp/examples/mcp/mcp_server.py and uvx cannot find the script. This only works when invoked from the repo root with an explicit project path; use a project-relative path like mcp_server.py (or set a cwd) so the example works when run from examples/mcp.

Useful? React with 👍 / 👎.

]
21 changes: 21 additions & 0 deletions examples/mcp/mcp_server.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
"""Simple MCP server for the mcp example."""

from fastmcp import FastMCP

mcp = FastMCP("demo")


@mcp.resource(uri="demo://greeting/{name}")
def greeting(name: str) -> str:
"""Get a personalized greeting."""
return f"Hello, {name}! Welcome to the demo."


@mcp.prompt()
def summarize(style: str = "brief") -> str:
"""Summarization guidance."""
return f"Summarize the following in a {style} style."


if __name__ == "__main__":
mcp.run()
13 changes: 13 additions & 0 deletions examples/mcp/models/greeting.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
name: MCP Greeting
description: Demonstrates MCP resource and prompt usage
---
# MCP Demo

## Resource Example

{{ colin.mcp.demo.resource('demo://greeting/World') }}

## Prompt Example

{{ colin.mcp.demo.prompt('summarize', style='detailed') }}
9 changes: 9 additions & 0 deletions examples/mcp/output/greeting.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# MCP Demo

## Resource Example

Hello, World! Welcome to the demo.

## Prompt Example

Summarize the following in a detailed style.
7 changes: 5 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ dependencies = [
"rich>=14.2.0",
"tomli>=2.3.0",
"tomli-w>=1.2.0",
"fastmcp>=2.14.1",
"fastmcp @ git+https://github.com/jlowin/fastmcp@main",
"pydantic-ai>=1.39.0",
"httpx>=0.27.0",
]
Expand Down Expand Up @@ -42,7 +42,7 @@ env = [
"COLIN_DEFAULT_LLM_MODEL=test",
]
filterwarnings = [
"ignore::pytest.PytestUnraisableExceptionWarning:.*Event loop is closed.*",
"ignore:.*BaseSubprocessTransport.*:pytest.PytestUnraisableExceptionWarning",
]

[tool.ty.terminal]
Expand All @@ -59,6 +59,9 @@ select = ["E", "F", "I", "UP"]
requires = ["hatchling", "uv-dynamic-versioning"]
build-backend = "hatchling.build"

[tool.hatch.metadata]
allow-direct-references = true

[tool.hatch.version]
source = "uv-dynamic-versioning"

Expand Down
11 changes: 7 additions & 4 deletions src/colin/compiler/engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -485,17 +485,20 @@ def _load_document(self, path: Path) -> ColinDocument:
content = path.read_text(encoding="utf-8")
post = fm_parser.loads(content)

# Build relative path for URIs and error messages
relative = path.relative_to(self.config.model_path)

# Extract colin config
raw_colin = post.metadata.pop("colin", {})
colin_data = cast(dict[str, Any], raw_colin) if isinstance(raw_colin, dict) else {}
colin_config = ColinConfig.model_validate(colin_data)
try:
colin_config = ColinConfig.model_validate(colin_data)
except Exception as e:
raise ValueError(f"Invalid frontmatter in {relative}:\n{e}") from e

# Rest is document metadata
metadata = cast(dict[str, Any], post.metadata)
frontmatter = Frontmatter(colin=colin_config, metadata=metadata)

# Build URI from path
relative = path.relative_to(self.config.model_path)
uri = f"project://{relative}"

# Hash the FULL content (including frontmatter) for change detection
Expand Down
4 changes: 2 additions & 2 deletions src/colin/providers/base.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
"""Provider base class."""

import hashlib
import json
from collections.abc import AsyncIterator, Awaitable, Callable
from contextlib import asynccontextmanager
from typing import TYPE_CHECKING, Any, ClassVar
Expand Down Expand Up @@ -94,8 +96,6 @@ def from_config(cls, name: str | None, config: dict[str, Any]) -> Self:
Returns:
Configured provider instance.
"""
import hashlib
import json

instance = cls(**config)
instance._connection = name or ""
Expand Down
4 changes: 4 additions & 0 deletions src/colin/providers/mcp.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,10 @@ def from_config(cls, name: str | None, config: dict[str, Any]) -> Self:
"""
if not name:
raise ValueError("MCP provider requires an instance name")
# Set keep_alive=False for stdio servers to ensure proper subprocess cleanup
# and avoid "Event loop is closed" warnings during shutdown.
if "command" in config and "keep_alive" not in config:
config = {**config, "keep_alive": False}
server = MCPServerAdapter.validate_python(config)
return cls(name, server)

Expand Down
10 changes: 3 additions & 7 deletions uv.lock

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