@ansvar/ch-organic-regen-mcp 0.1.0
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.
- package/.github/workflows/check-freshness.yml +49 -0
- package/.github/workflows/ci.yml +21 -0
- package/.github/workflows/codeql.yml +25 -0
- package/.github/workflows/ghcr-build.yml +45 -0
- package/.github/workflows/gitleaks.yml +18 -0
- package/.github/workflows/ingest.yml +59 -0
- package/.github/workflows/publish.yml +24 -0
- package/CHANGELOG.md +15 -0
- package/CODEOWNERS +1 -0
- package/COVERAGE.md +47 -0
- package/DISCLAIMER.md +67 -0
- package/Dockerfile +26 -0
- package/LICENSE +17 -0
- package/PRIVACY.md +23 -0
- package/README.md +117 -0
- package/SECURITY.md +25 -0
- package/TOOLS.md +141 -0
- package/data/coverage.json +22 -0
- package/data/database.db +0 -0
- package/data/sources.yml +36 -0
- package/dist/db.d.ts +25 -0
- package/dist/db.d.ts.map +1 -0
- package/dist/db.js +184 -0
- package/dist/db.js.map +1 -0
- package/dist/http-server.d.ts +2 -0
- package/dist/http-server.d.ts.map +1 -0
- package/dist/http-server.js +263 -0
- package/dist/http-server.js.map +1 -0
- package/dist/jurisdiction.d.ts +18 -0
- package/dist/jurisdiction.d.ts.map +1 -0
- package/dist/jurisdiction.js +16 -0
- package/dist/jurisdiction.js.map +1 -0
- package/dist/metadata.d.ts +11 -0
- package/dist/metadata.d.ts.map +1 -0
- package/dist/metadata.js +31 -0
- package/dist/metadata.js.map +1 -0
- package/dist/server.d.ts +3 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +209 -0
- package/dist/server.js.map +1 -0
- package/dist/tools/about.d.ts +15 -0
- package/dist/tools/about.d.ts.map +1 -0
- package/dist/tools/about.js +27 -0
- package/dist/tools/about.js.map +1 -0
- package/dist/tools/check-freshness.d.ts +15 -0
- package/dist/tools/check-freshness.d.ts.map +1 -0
- package/dist/tools/check-freshness.js +26 -0
- package/dist/tools/check-freshness.js.map +1 -0
- package/dist/tools/get-approved-inputs.d.ts +26 -0
- package/dist/tools/get-approved-inputs.d.ts.map +1 -0
- package/dist/tools/get-approved-inputs.js +28 -0
- package/dist/tools/get-approved-inputs.js.map +1 -0
- package/dist/tools/get-conversion-requirements.d.ts +28 -0
- package/dist/tools/get-conversion-requirements.d.ts.map +1 -0
- package/dist/tools/get-conversion-requirements.js +32 -0
- package/dist/tools/get-conversion-requirements.js.map +1 -0
- package/dist/tools/get-organic-standards.d.ts +27 -0
- package/dist/tools/get-organic-standards.d.ts.map +1 -0
- package/dist/tools/get-organic-standards.js +31 -0
- package/dist/tools/get-organic-standards.js.map +1 -0
- package/dist/tools/get-organic-subsidies.d.ts +28 -0
- package/dist/tools/get-organic-subsidies.d.ts.map +1 -0
- package/dist/tools/get-organic-subsidies.js +32 -0
- package/dist/tools/get-organic-subsidies.js.map +1 -0
- package/dist/tools/get-soil-health-guidance.d.ts +25 -0
- package/dist/tools/get-soil-health-guidance.d.ts.map +1 -0
- package/dist/tools/get-soil-health-guidance.js +27 -0
- package/dist/tools/get-soil-health-guidance.js.map +1 -0
- package/dist/tools/list-sources.d.ts +18 -0
- package/dist/tools/list-sources.d.ts.map +1 -0
- package/dist/tools/list-sources.js +61 -0
- package/dist/tools/list-sources.js.map +1 -0
- package/dist/tools/search-certification-guidance.d.ts +24 -0
- package/dist/tools/search-certification-guidance.d.ts.map +1 -0
- package/dist/tools/search-certification-guidance.js +24 -0
- package/dist/tools/search-certification-guidance.js.map +1 -0
- package/dist/tools/search-organic-rules.d.ts +25 -0
- package/dist/tools/search-organic-rules.d.ts.map +1 -0
- package/dist/tools/search-organic-rules.js +26 -0
- package/dist/tools/search-organic-rules.js.map +1 -0
- package/docker-compose.yml +12 -0
- package/eslint.config.js +26 -0
- package/package.json +54 -0
- package/scripts/ingest.ts +963 -0
- package/server.json +16 -0
- package/src/db.ts +225 -0
- package/src/http-server.ts +302 -0
- package/src/jurisdiction.ts +30 -0
- package/src/metadata.ts +45 -0
- package/src/server.ts +239 -0
- package/src/tools/about.ts +28 -0
- package/src/tools/check-freshness.ts +42 -0
- package/src/tools/get-approved-inputs.ts +44 -0
- package/src/tools/get-conversion-requirements.ts +50 -0
- package/src/tools/get-organic-standards.ts +48 -0
- package/src/tools/get-organic-subsidies.ts +50 -0
- package/src/tools/get-soil-health-guidance.ts +42 -0
- package/src/tools/list-sources.ts +75 -0
- package/src/tools/search-certification-guidance.ts +41 -0
- package/src/tools/search-organic-rules.ts +35 -0
- package/tests/db.test.ts +96 -0
- package/tests/helpers/seed-db.ts +145 -0
- package/tests/jurisdiction.test.ts +35 -0
- package/tests/tools/about.test.ts +22 -0
- package/tests/tools/check-freshness.test.ts +57 -0
- package/tests/tools/list-sources.test.ts +55 -0
- package/tests/tools/search-organic-rules.test.ts +56 -0
- package/tsconfig.json +19 -0
package/src/server.ts
ADDED
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
4
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
5
|
+
import {
|
|
6
|
+
ListToolsRequestSchema,
|
|
7
|
+
CallToolRequestSchema,
|
|
8
|
+
} from '@modelcontextprotocol/sdk/types.js';
|
|
9
|
+
import { z } from 'zod';
|
|
10
|
+
import { createDatabase } from './db.js';
|
|
11
|
+
import { handleAbout } from './tools/about.js';
|
|
12
|
+
import { handleListSources } from './tools/list-sources.js';
|
|
13
|
+
import { handleCheckFreshness } from './tools/check-freshness.js';
|
|
14
|
+
import { handleSearchOrganicRules } from './tools/search-organic-rules.js';
|
|
15
|
+
import { handleGetConversionRequirements } from './tools/get-conversion-requirements.js';
|
|
16
|
+
import { handleGetOrganicStandards } from './tools/get-organic-standards.js';
|
|
17
|
+
import { handleGetApprovedInputs } from './tools/get-approved-inputs.js';
|
|
18
|
+
import { handleGetOrganicSubsidies } from './tools/get-organic-subsidies.js';
|
|
19
|
+
import { handleGetSoilHealthGuidance } from './tools/get-soil-health-guidance.js';
|
|
20
|
+
import { handleSearchCertificationGuidance } from './tools/search-certification-guidance.js';
|
|
21
|
+
|
|
22
|
+
const SERVER_NAME = 'ch-organic-regen-mcp';
|
|
23
|
+
const SERVER_VERSION = '0.1.0';
|
|
24
|
+
|
|
25
|
+
const TOOLS = [
|
|
26
|
+
{
|
|
27
|
+
name: 'about',
|
|
28
|
+
description: 'Get server metadata: name, version, coverage, data sources, and links.',
|
|
29
|
+
inputSchema: { type: 'object' as const, properties: {} },
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
name: 'list_sources',
|
|
33
|
+
description: 'List all data sources with authority, URL, license, and freshness info.',
|
|
34
|
+
inputSchema: { type: 'object' as const, properties: {} },
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
name: 'check_data_freshness',
|
|
38
|
+
description: 'Check when data was last ingested, staleness status, and how to trigger a refresh.',
|
|
39
|
+
inputSchema: { type: 'object' as const, properties: {} },
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
name: 'search_organic_rules',
|
|
43
|
+
description:
|
|
44
|
+
'Volltextsuche ueber Bio-Richtlinien, Umstellung, Bodenfruchtbarkeit und regenerative Landwirtschaft. ' +
|
|
45
|
+
'Search organic farming rules, conversion guidance, soil health, and regenerative agriculture topics.',
|
|
46
|
+
inputSchema: {
|
|
47
|
+
type: 'object' as const,
|
|
48
|
+
properties: {
|
|
49
|
+
query: { type: 'string', description: 'Suchbegriff / free-text search query (German or English)' },
|
|
50
|
+
topic: { type: 'string', description: 'Filter by topic (e.g. knospe, demeter, umstellung, bodenfruchtbarkeit)' },
|
|
51
|
+
jurisdiction: { type: 'string', description: 'ISO 3166-1 alpha-2 code (default: CH)' },
|
|
52
|
+
limit: { type: 'number', description: 'Max results (default: 20, max: 50)' },
|
|
53
|
+
},
|
|
54
|
+
required: ['query'],
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
name: 'get_conversion_requirements',
|
|
59
|
+
description:
|
|
60
|
+
'Umstellungsanforderungen fuer den Biobetrieb: Zeitrahmen, Voraussetzungen, Unterstuetzungsmassnahmen. ' +
|
|
61
|
+
'Get organic conversion timeline, requirements, and support measures by farm type.',
|
|
62
|
+
inputSchema: {
|
|
63
|
+
type: 'object' as const,
|
|
64
|
+
properties: {
|
|
65
|
+
farm_type: { type: 'string', description: 'Betriebstyp (e.g. ackerbau, milchwirtschaft, obstbau, rebbau, gemuese)' },
|
|
66
|
+
current_system: { type: 'string', description: 'Aktuelles System (e.g. oeln, konventionell, ip)' },
|
|
67
|
+
jurisdiction: { type: 'string', description: 'ISO 3166-1 alpha-2 code (default: CH)' },
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
name: 'get_organic_standards',
|
|
73
|
+
description:
|
|
74
|
+
'Bio-Richtlinien nach Standard (Knospe, Bio-Verordnung, Demeter) und Produktionsbereich. ' +
|
|
75
|
+
'Get organic production rules comparing Bio Suisse Knospe, federal Bio-Verordnung, and Demeter standards.',
|
|
76
|
+
inputSchema: {
|
|
77
|
+
type: 'object' as const,
|
|
78
|
+
properties: {
|
|
79
|
+
production_type: { type: 'string', description: 'Produktionsbereich (e.g. tierhaltung, pflanzenbau, verarbeitung, futtermittel)' },
|
|
80
|
+
standard: { type: 'string', description: 'Standard: bio_verordnung, knospe, demeter' },
|
|
81
|
+
jurisdiction: { type: 'string', description: 'ISO 3166-1 alpha-2 code (default: CH)' },
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
name: 'get_approved_inputs',
|
|
87
|
+
description:
|
|
88
|
+
'Zugelassene Betriebsmittel fuer Biolandbau (FiBL-Liste): Duenger, Pflanzenschutz, Futtermittel. ' +
|
|
89
|
+
'Get approved inputs for organic farming from the FiBL Betriebsmittelliste.',
|
|
90
|
+
inputSchema: {
|
|
91
|
+
type: 'object' as const,
|
|
92
|
+
properties: {
|
|
93
|
+
input_type: { type: 'string', description: 'Betriebsmitteltyp: duenger, pflanzenschutz, futtermittel' },
|
|
94
|
+
jurisdiction: { type: 'string', description: 'ISO 3166-1 alpha-2 code (default: CH)' },
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
name: 'get_organic_subsidies',
|
|
100
|
+
description:
|
|
101
|
+
'Bio-Beitrag und weitere Direktzahlungen fuer Biobetriebe: Beitragshoehe, Bedingungen, Kumulierung. ' +
|
|
102
|
+
'Get organic farming subsidies (Bio-Beitrag) rates, conditions, and stacking rules.',
|
|
103
|
+
inputSchema: {
|
|
104
|
+
type: 'object' as const,
|
|
105
|
+
properties: {
|
|
106
|
+
subsidy_type: { type: 'string', description: 'Beitragstyp (e.g. bio_beitrag, extenso, bff, tierwohl)' },
|
|
107
|
+
zone: { type: 'string', description: 'Zone: talzone, huegelzone, bergzone_i, bergzone_ii, bergzone_iii, bergzone_iv' },
|
|
108
|
+
jurisdiction: { type: 'string', description: 'ISO 3166-1 alpha-2 code (default: CH)' },
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
name: 'get_soil_health_guidance',
|
|
114
|
+
description:
|
|
115
|
+
'Bodenfruchtbarkeit und regenerative Methoden: Kompost, Gruenduengung, Fruchtfolge, Conservation Agriculture. ' +
|
|
116
|
+
'Get soil health and regenerative agriculture guidance: composting, cover crops, rotation, agroforestry.',
|
|
117
|
+
inputSchema: {
|
|
118
|
+
type: 'object' as const,
|
|
119
|
+
properties: {
|
|
120
|
+
topic: { type: 'string', description: 'Thema (e.g. kompost, gruenduengung, fruchtfolge, agroforstwirtschaft, untersaat, direktsaat)' },
|
|
121
|
+
jurisdiction: { type: 'string', description: 'ISO 3166-1 alpha-2 code (default: CH)' },
|
|
122
|
+
},
|
|
123
|
+
},
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
name: 'search_certification_guidance',
|
|
127
|
+
description:
|
|
128
|
+
'Zertifizierungsprozess Bio Suisse Knospe: Schritte, Inspektionsstellen, Kosten, Haeufigkeit. ' +
|
|
129
|
+
'Search organic certification guidance: bio.inspecta, Bio Test Agro process, Knospe application steps.',
|
|
130
|
+
inputSchema: {
|
|
131
|
+
type: 'object' as const,
|
|
132
|
+
properties: {
|
|
133
|
+
query: { type: 'string', description: 'Suchbegriff (e.g. anmeldung, kontrolle, kosten, inspektion)' },
|
|
134
|
+
jurisdiction: { type: 'string', description: 'ISO 3166-1 alpha-2 code (default: CH)' },
|
|
135
|
+
},
|
|
136
|
+
required: ['query'],
|
|
137
|
+
},
|
|
138
|
+
},
|
|
139
|
+
];
|
|
140
|
+
|
|
141
|
+
const SearchOrganicRulesSchema = z.object({
|
|
142
|
+
query: z.string(),
|
|
143
|
+
topic: z.string().optional(),
|
|
144
|
+
jurisdiction: z.string().optional(),
|
|
145
|
+
limit: z.number().optional(),
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
const ConversionArgsSchema = z.object({
|
|
149
|
+
farm_type: z.string().optional(),
|
|
150
|
+
current_system: z.string().optional(),
|
|
151
|
+
jurisdiction: z.string().optional(),
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
const StandardsArgsSchema = z.object({
|
|
155
|
+
production_type: z.string().optional(),
|
|
156
|
+
standard: z.string().optional(),
|
|
157
|
+
jurisdiction: z.string().optional(),
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
const InputsArgsSchema = z.object({
|
|
161
|
+
input_type: z.string().optional(),
|
|
162
|
+
jurisdiction: z.string().optional(),
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
const SubsidiesArgsSchema = z.object({
|
|
166
|
+
subsidy_type: z.string().optional(),
|
|
167
|
+
zone: z.string().optional(),
|
|
168
|
+
jurisdiction: z.string().optional(),
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
const SoilHealthArgsSchema = z.object({
|
|
172
|
+
topic: z.string().optional(),
|
|
173
|
+
jurisdiction: z.string().optional(),
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
const CertArgsSchema = z.object({
|
|
177
|
+
query: z.string(),
|
|
178
|
+
jurisdiction: z.string().optional(),
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
function textResult(data: unknown) {
|
|
182
|
+
return { content: [{ type: 'text' as const, text: JSON.stringify(data, null, 2) }] };
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function errorResult(message: string) {
|
|
186
|
+
return { content: [{ type: 'text' as const, text: JSON.stringify({ error: message }) }], isError: true };
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const db = createDatabase();
|
|
190
|
+
|
|
191
|
+
const server = new Server(
|
|
192
|
+
{ name: SERVER_NAME, version: SERVER_VERSION },
|
|
193
|
+
{ capabilities: { tools: {} } }
|
|
194
|
+
);
|
|
195
|
+
|
|
196
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: TOOLS }));
|
|
197
|
+
|
|
198
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
199
|
+
const { name, arguments: args = {} } = request.params;
|
|
200
|
+
|
|
201
|
+
try {
|
|
202
|
+
switch (name) {
|
|
203
|
+
case 'about':
|
|
204
|
+
return textResult(handleAbout());
|
|
205
|
+
case 'list_sources':
|
|
206
|
+
return textResult(handleListSources(db));
|
|
207
|
+
case 'check_data_freshness':
|
|
208
|
+
return textResult(handleCheckFreshness(db));
|
|
209
|
+
case 'search_organic_rules':
|
|
210
|
+
return textResult(handleSearchOrganicRules(db, SearchOrganicRulesSchema.parse(args)));
|
|
211
|
+
case 'get_conversion_requirements':
|
|
212
|
+
return textResult(handleGetConversionRequirements(db, ConversionArgsSchema.parse(args)));
|
|
213
|
+
case 'get_organic_standards':
|
|
214
|
+
return textResult(handleGetOrganicStandards(db, StandardsArgsSchema.parse(args)));
|
|
215
|
+
case 'get_approved_inputs':
|
|
216
|
+
return textResult(handleGetApprovedInputs(db, InputsArgsSchema.parse(args)));
|
|
217
|
+
case 'get_organic_subsidies':
|
|
218
|
+
return textResult(handleGetOrganicSubsidies(db, SubsidiesArgsSchema.parse(args)));
|
|
219
|
+
case 'get_soil_health_guidance':
|
|
220
|
+
return textResult(handleGetSoilHealthGuidance(db, SoilHealthArgsSchema.parse(args)));
|
|
221
|
+
case 'search_certification_guidance':
|
|
222
|
+
return textResult(handleSearchCertificationGuidance(db, CertArgsSchema.parse(args)));
|
|
223
|
+
default:
|
|
224
|
+
return errorResult(`Unknown tool: ${name}`);
|
|
225
|
+
}
|
|
226
|
+
} catch (err) {
|
|
227
|
+
return errorResult(err instanceof Error ? err.message : String(err));
|
|
228
|
+
}
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
async function main(): Promise<void> {
|
|
232
|
+
const transport = new StdioServerTransport();
|
|
233
|
+
await server.connect(transport);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
main().catch((err) => {
|
|
237
|
+
process.stderr.write(`Fatal error: ${err.message}\n`);
|
|
238
|
+
process.exit(1);
|
|
239
|
+
});
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { buildMeta } from '../metadata.js';
|
|
2
|
+
import { SUPPORTED_JURISDICTIONS } from '../jurisdiction.js';
|
|
3
|
+
|
|
4
|
+
export function handleAbout() {
|
|
5
|
+
return {
|
|
6
|
+
name: 'Switzerland Organic & Regenerative Farming MCP',
|
|
7
|
+
description:
|
|
8
|
+
'Swiss organic and regenerative agriculture data: Bio Suisse Knospe standards, Bio-Verordnung (SR 910.18), ' +
|
|
9
|
+
'Demeter biodynamic rules, FiBL Betriebsmittelliste (approved inputs), conversion requirements, ' +
|
|
10
|
+
'organic subsidies (Bio-Beitrag), soil health guidance, and certification processes (bio.inspecta, Bio Test Agro).',
|
|
11
|
+
version: '0.1.0',
|
|
12
|
+
jurisdiction: [...SUPPORTED_JURISDICTIONS],
|
|
13
|
+
data_sources: [
|
|
14
|
+
'Bio Suisse Richtlinien (Knospe)',
|
|
15
|
+
'Bio-Verordnung SR 910.18 (BLW)',
|
|
16
|
+
'FiBL Betriebsmittelliste',
|
|
17
|
+
'Demeter Schweiz Richtlinien',
|
|
18
|
+
'AGRIDEA Umstellungsberatung',
|
|
19
|
+
],
|
|
20
|
+
tools_count: 10,
|
|
21
|
+
links: {
|
|
22
|
+
homepage: 'https://ansvar.eu/open-agriculture',
|
|
23
|
+
repository: 'https://github.com/ansvar-systems/ch-organic-regen-mcp',
|
|
24
|
+
mcp_network: 'https://ansvar.ai/mcp',
|
|
25
|
+
},
|
|
26
|
+
_meta: buildMeta(),
|
|
27
|
+
};
|
|
28
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { buildMeta } from '../metadata.js';
|
|
2
|
+
import type { Database } from '../db.js';
|
|
3
|
+
|
|
4
|
+
interface FreshnessResult {
|
|
5
|
+
status: 'fresh' | 'stale' | 'unknown';
|
|
6
|
+
last_ingest: string | null;
|
|
7
|
+
build_date: string | null;
|
|
8
|
+
schema_version: string | null;
|
|
9
|
+
days_since_ingest: number | null;
|
|
10
|
+
staleness_threshold_days: number;
|
|
11
|
+
refresh_command: string;
|
|
12
|
+
_meta: ReturnType<typeof buildMeta>;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const STALENESS_THRESHOLD_DAYS = 90;
|
|
16
|
+
|
|
17
|
+
export function handleCheckFreshness(db: Database): FreshnessResult {
|
|
18
|
+
const lastIngest = db.get<{ value: string }>('SELECT value FROM db_metadata WHERE key = ?', ['last_ingest']);
|
|
19
|
+
const buildDate = db.get<{ value: string }>('SELECT value FROM db_metadata WHERE key = ?', ['build_date']);
|
|
20
|
+
const schemaVersion = db.get<{ value: string }>('SELECT value FROM db_metadata WHERE key = ?', ['schema_version']);
|
|
21
|
+
|
|
22
|
+
let status: 'fresh' | 'stale' | 'unknown' = 'unknown';
|
|
23
|
+
let daysSinceIngest: number | null = null;
|
|
24
|
+
|
|
25
|
+
if (lastIngest?.value) {
|
|
26
|
+
const ingestDate = new Date(lastIngest.value);
|
|
27
|
+
const now = new Date();
|
|
28
|
+
daysSinceIngest = Math.floor((now.getTime() - ingestDate.getTime()) / (1000 * 60 * 60 * 24));
|
|
29
|
+
status = daysSinceIngest <= STALENESS_THRESHOLD_DAYS ? 'fresh' : 'stale';
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return {
|
|
33
|
+
status,
|
|
34
|
+
last_ingest: lastIngest?.value ?? null,
|
|
35
|
+
build_date: buildDate?.value ?? null,
|
|
36
|
+
schema_version: schemaVersion?.value ?? null,
|
|
37
|
+
days_since_ingest: daysSinceIngest,
|
|
38
|
+
staleness_threshold_days: STALENESS_THRESHOLD_DAYS,
|
|
39
|
+
refresh_command: 'gh workflow run ingest.yml -R ansvar-systems/ch-organic-regen-mcp',
|
|
40
|
+
_meta: buildMeta(),
|
|
41
|
+
};
|
|
42
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { buildMeta } from '../metadata.js';
|
|
2
|
+
import { validateJurisdiction } from '../jurisdiction.js';
|
|
3
|
+
import type { Database } from '../db.js';
|
|
4
|
+
|
|
5
|
+
interface InputsArgs {
|
|
6
|
+
input_type?: string;
|
|
7
|
+
jurisdiction?: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function handleGetApprovedInputs(db: Database, args: InputsArgs) {
|
|
11
|
+
const jv = validateJurisdiction(args.jurisdiction);
|
|
12
|
+
if (!jv.valid) return jv.error;
|
|
13
|
+
|
|
14
|
+
let sql = 'SELECT * FROM approved_inputs WHERE jurisdiction = ?';
|
|
15
|
+
const params: unknown[] = [jv.jurisdiction];
|
|
16
|
+
|
|
17
|
+
if (args.input_type) {
|
|
18
|
+
sql += ' AND LOWER(input_type) LIKE LOWER(?)';
|
|
19
|
+
params.push(`%${args.input_type}%`);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const rows = db.all<{
|
|
23
|
+
id: number;
|
|
24
|
+
input_type: string;
|
|
25
|
+
product_name: string;
|
|
26
|
+
description: string;
|
|
27
|
+
restrictions: string;
|
|
28
|
+
source: string;
|
|
29
|
+
}>(sql, params);
|
|
30
|
+
|
|
31
|
+
return {
|
|
32
|
+
jurisdiction: jv.jurisdiction,
|
|
33
|
+
filters: { input_type: args.input_type },
|
|
34
|
+
results_count: rows.length,
|
|
35
|
+
results: rows.map(r => ({
|
|
36
|
+
input_type: r.input_type,
|
|
37
|
+
product_name: r.product_name,
|
|
38
|
+
description: r.description,
|
|
39
|
+
restrictions: r.restrictions,
|
|
40
|
+
source: r.source,
|
|
41
|
+
})),
|
|
42
|
+
_meta: buildMeta({ source_url: 'https://www.betriebsmittelliste.ch' }),
|
|
43
|
+
};
|
|
44
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { buildMeta } from '../metadata.js';
|
|
2
|
+
import { validateJurisdiction } from '../jurisdiction.js';
|
|
3
|
+
import type { Database } from '../db.js';
|
|
4
|
+
|
|
5
|
+
interface ConversionArgs {
|
|
6
|
+
farm_type?: string;
|
|
7
|
+
current_system?: string;
|
|
8
|
+
jurisdiction?: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function handleGetConversionRequirements(db: Database, args: ConversionArgs) {
|
|
12
|
+
const jv = validateJurisdiction(args.jurisdiction);
|
|
13
|
+
if (!jv.valid) return jv.error;
|
|
14
|
+
|
|
15
|
+
let sql = 'SELECT * FROM conversion_requirements WHERE jurisdiction = ?';
|
|
16
|
+
const params: unknown[] = [jv.jurisdiction];
|
|
17
|
+
|
|
18
|
+
if (args.farm_type) {
|
|
19
|
+
sql += ' AND LOWER(farm_type) LIKE LOWER(?)';
|
|
20
|
+
params.push(`%${args.farm_type}%`);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (args.current_system) {
|
|
24
|
+
sql += ' AND LOWER(current_system) LIKE LOWER(?)';
|
|
25
|
+
params.push(`%${args.current_system}%`);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const rows = db.all<{
|
|
29
|
+
id: number;
|
|
30
|
+
farm_type: string;
|
|
31
|
+
current_system: string;
|
|
32
|
+
timeline_years: number;
|
|
33
|
+
requirements: string;
|
|
34
|
+
support_measures: string;
|
|
35
|
+
}>(sql, params);
|
|
36
|
+
|
|
37
|
+
return {
|
|
38
|
+
jurisdiction: jv.jurisdiction,
|
|
39
|
+
filters: { farm_type: args.farm_type, current_system: args.current_system },
|
|
40
|
+
results_count: rows.length,
|
|
41
|
+
results: rows.map(r => ({
|
|
42
|
+
farm_type: r.farm_type,
|
|
43
|
+
current_system: r.current_system,
|
|
44
|
+
timeline_years: r.timeline_years,
|
|
45
|
+
requirements: r.requirements,
|
|
46
|
+
support_measures: r.support_measures,
|
|
47
|
+
})),
|
|
48
|
+
_meta: buildMeta({ source_url: 'https://www.bio-suisse.ch/de/umstellung' }),
|
|
49
|
+
};
|
|
50
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { buildMeta } from '../metadata.js';
|
|
2
|
+
import { validateJurisdiction } from '../jurisdiction.js';
|
|
3
|
+
import type { Database } from '../db.js';
|
|
4
|
+
|
|
5
|
+
interface StandardsArgs {
|
|
6
|
+
production_type?: string;
|
|
7
|
+
standard?: string;
|
|
8
|
+
jurisdiction?: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function handleGetOrganicStandards(db: Database, args: StandardsArgs) {
|
|
12
|
+
const jv = validateJurisdiction(args.jurisdiction);
|
|
13
|
+
if (!jv.valid) return jv.error;
|
|
14
|
+
|
|
15
|
+
let sql = 'SELECT * FROM organic_standards WHERE jurisdiction = ?';
|
|
16
|
+
const params: unknown[] = [jv.jurisdiction];
|
|
17
|
+
|
|
18
|
+
if (args.production_type) {
|
|
19
|
+
sql += ' AND LOWER(production_type) LIKE LOWER(?)';
|
|
20
|
+
params.push(`%${args.production_type}%`);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (args.standard) {
|
|
24
|
+
sql += ' AND LOWER(standard) LIKE LOWER(?)';
|
|
25
|
+
params.push(`%${args.standard}%`);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const rows = db.all<{
|
|
29
|
+
id: number;
|
|
30
|
+
production_type: string;
|
|
31
|
+
standard: string;
|
|
32
|
+
rule: string;
|
|
33
|
+
description: string;
|
|
34
|
+
}>(sql, params);
|
|
35
|
+
|
|
36
|
+
return {
|
|
37
|
+
jurisdiction: jv.jurisdiction,
|
|
38
|
+
filters: { production_type: args.production_type, standard: args.standard },
|
|
39
|
+
results_count: rows.length,
|
|
40
|
+
results: rows.map(r => ({
|
|
41
|
+
production_type: r.production_type,
|
|
42
|
+
standard: r.standard,
|
|
43
|
+
rule: r.rule,
|
|
44
|
+
description: r.description,
|
|
45
|
+
})),
|
|
46
|
+
_meta: buildMeta({ source_url: 'https://www.bio-suisse.ch/de/richtlinien' }),
|
|
47
|
+
};
|
|
48
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { buildMeta } from '../metadata.js';
|
|
2
|
+
import { validateJurisdiction } from '../jurisdiction.js';
|
|
3
|
+
import type { Database } from '../db.js';
|
|
4
|
+
|
|
5
|
+
interface SubsidiesArgs {
|
|
6
|
+
subsidy_type?: string;
|
|
7
|
+
zone?: string;
|
|
8
|
+
jurisdiction?: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function handleGetOrganicSubsidies(db: Database, args: SubsidiesArgs) {
|
|
12
|
+
const jv = validateJurisdiction(args.jurisdiction);
|
|
13
|
+
if (!jv.valid) return jv.error;
|
|
14
|
+
|
|
15
|
+
let sql = 'SELECT * FROM organic_subsidies WHERE jurisdiction = ?';
|
|
16
|
+
const params: unknown[] = [jv.jurisdiction];
|
|
17
|
+
|
|
18
|
+
if (args.subsidy_type) {
|
|
19
|
+
sql += ' AND LOWER(subsidy_type) LIKE LOWER(?)';
|
|
20
|
+
params.push(`%${args.subsidy_type}%`);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (args.zone) {
|
|
24
|
+
sql += ' AND (LOWER(zone) LIKE LOWER(?) OR zone IS NULL)';
|
|
25
|
+
params.push(`%${args.zone}%`);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const rows = db.all<{
|
|
29
|
+
id: number;
|
|
30
|
+
subsidy_type: string;
|
|
31
|
+
zone: string;
|
|
32
|
+
rate_chf_ha: number;
|
|
33
|
+
conditions: string;
|
|
34
|
+
stacking_rules: string;
|
|
35
|
+
}>(sql, params);
|
|
36
|
+
|
|
37
|
+
return {
|
|
38
|
+
jurisdiction: jv.jurisdiction,
|
|
39
|
+
filters: { subsidy_type: args.subsidy_type, zone: args.zone },
|
|
40
|
+
results_count: rows.length,
|
|
41
|
+
results: rows.map(r => ({
|
|
42
|
+
subsidy_type: r.subsidy_type,
|
|
43
|
+
zone: r.zone,
|
|
44
|
+
rate_chf_ha: r.rate_chf_ha,
|
|
45
|
+
conditions: r.conditions,
|
|
46
|
+
stacking_rules: r.stacking_rules,
|
|
47
|
+
})),
|
|
48
|
+
_meta: buildMeta({ source_url: 'https://www.blw.admin.ch/blw/de/home/instrumente/direktzahlungen/produktionssystembeitraege.html' }),
|
|
49
|
+
};
|
|
50
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { buildMeta } from '../metadata.js';
|
|
2
|
+
import { validateJurisdiction } from '../jurisdiction.js';
|
|
3
|
+
import type { Database } from '../db.js';
|
|
4
|
+
|
|
5
|
+
interface SoilHealthArgs {
|
|
6
|
+
topic?: string;
|
|
7
|
+
jurisdiction?: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function handleGetSoilHealthGuidance(db: Database, args: SoilHealthArgs) {
|
|
11
|
+
const jv = validateJurisdiction(args.jurisdiction);
|
|
12
|
+
if (!jv.valid) return jv.error;
|
|
13
|
+
|
|
14
|
+
let sql = 'SELECT * FROM soil_health WHERE jurisdiction = ?';
|
|
15
|
+
const params: unknown[] = [jv.jurisdiction];
|
|
16
|
+
|
|
17
|
+
if (args.topic) {
|
|
18
|
+
sql += ' AND LOWER(topic) LIKE LOWER(?)';
|
|
19
|
+
params.push(`%${args.topic}%`);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const rows = db.all<{
|
|
23
|
+
id: number;
|
|
24
|
+
topic: string;
|
|
25
|
+
guidance: string;
|
|
26
|
+
technique: string;
|
|
27
|
+
benefits: string;
|
|
28
|
+
}>(sql, params);
|
|
29
|
+
|
|
30
|
+
return {
|
|
31
|
+
jurisdiction: jv.jurisdiction,
|
|
32
|
+
filters: { topic: args.topic },
|
|
33
|
+
results_count: rows.length,
|
|
34
|
+
results: rows.map(r => ({
|
|
35
|
+
topic: r.topic,
|
|
36
|
+
guidance: r.guidance,
|
|
37
|
+
technique: r.technique,
|
|
38
|
+
benefits: r.benefits,
|
|
39
|
+
})),
|
|
40
|
+
_meta: buildMeta({ source_url: 'https://www.fibl.org/de/themen/bodenfruchtbarkeit' }),
|
|
41
|
+
};
|
|
42
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { buildMeta } from '../metadata.js';
|
|
2
|
+
import type { Database } from '../db.js';
|
|
3
|
+
|
|
4
|
+
interface Source {
|
|
5
|
+
name: string;
|
|
6
|
+
authority: string;
|
|
7
|
+
official_url: string;
|
|
8
|
+
retrieval_method: string;
|
|
9
|
+
update_frequency: string;
|
|
10
|
+
license: string;
|
|
11
|
+
coverage: string;
|
|
12
|
+
last_retrieved?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function handleListSources(db: Database): { sources: Source[]; _meta: ReturnType<typeof buildMeta> } {
|
|
16
|
+
const lastIngest = db.get<{ value: string }>('SELECT value FROM db_metadata WHERE key = ?', ['last_ingest']);
|
|
17
|
+
|
|
18
|
+
const sources: Source[] = [
|
|
19
|
+
{
|
|
20
|
+
name: 'Bio Suisse Richtlinien (Knospe)',
|
|
21
|
+
authority: 'Bio Suisse',
|
|
22
|
+
official_url: 'https://www.bio-suisse.ch/de/richtlinien',
|
|
23
|
+
retrieval_method: 'PDF_EXTRACT',
|
|
24
|
+
update_frequency: 'annual (Delegiertenversammlung)',
|
|
25
|
+
license: 'Bio Suisse — public standards document',
|
|
26
|
+
coverage: 'Knospe production rules: feed, animal welfare, processing, BFF, imports',
|
|
27
|
+
last_retrieved: lastIngest?.value,
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
name: 'Bio-Verordnung (SR 910.18)',
|
|
31
|
+
authority: 'Bundesamt fuer Landwirtschaft (BLW)',
|
|
32
|
+
official_url: 'https://www.fedlex.admin.ch/eli/cc/1997/2498_2498_2498/de',
|
|
33
|
+
retrieval_method: 'PDF_EXTRACT',
|
|
34
|
+
update_frequency: 'periodic (with ordinance revisions)',
|
|
35
|
+
license: 'Swiss Federal Administration — free reuse',
|
|
36
|
+
coverage: 'Federal organic farming regulation: minimum standards, conversion, labelling',
|
|
37
|
+
last_retrieved: lastIngest?.value,
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
name: 'FiBL Betriebsmittelliste',
|
|
41
|
+
authority: 'Forschungsinstitut fuer biologischen Landbau (FiBL)',
|
|
42
|
+
official_url: 'https://www.betriebsmittelliste.ch',
|
|
43
|
+
retrieval_method: 'ONLINE_REGISTER',
|
|
44
|
+
update_frequency: 'annual',
|
|
45
|
+
license: 'FiBL — public reference list',
|
|
46
|
+
coverage: 'Approved inputs for Swiss organic farming: fertilisers, plant protection, feed',
|
|
47
|
+
last_retrieved: lastIngest?.value,
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
name: 'Demeter Schweiz Richtlinien',
|
|
51
|
+
authority: 'Demeter Schweiz',
|
|
52
|
+
official_url: 'https://www.demeter.ch/richtlinien',
|
|
53
|
+
retrieval_method: 'PDF_EXTRACT',
|
|
54
|
+
update_frequency: 'periodic',
|
|
55
|
+
license: 'Demeter — public standards document',
|
|
56
|
+
coverage: 'Biodynamic farming rules: preparations 500-508, composting, livestock integration',
|
|
57
|
+
last_retrieved: lastIngest?.value,
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
name: 'Direktzahlungsverordnung (DZV) — Bio-Beitrag',
|
|
61
|
+
authority: 'Bundesamt fuer Landwirtschaft (BLW)',
|
|
62
|
+
official_url: 'https://www.blw.admin.ch/blw/de/home/instrumente/direktzahlungen/produktionssystembeitraege.html',
|
|
63
|
+
retrieval_method: 'PDF_EXTRACT',
|
|
64
|
+
update_frequency: 'annual (DZV updates)',
|
|
65
|
+
license: 'Swiss Federal Administration — free reuse',
|
|
66
|
+
coverage: 'Bio-Beitrag rates per land use type and altitude zone',
|
|
67
|
+
last_retrieved: lastIngest?.value,
|
|
68
|
+
},
|
|
69
|
+
];
|
|
70
|
+
|
|
71
|
+
return {
|
|
72
|
+
sources,
|
|
73
|
+
_meta: buildMeta(),
|
|
74
|
+
};
|
|
75
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { buildMeta } from '../metadata.js';
|
|
2
|
+
import { validateJurisdiction } from '../jurisdiction.js';
|
|
3
|
+
import type { Database } from '../db.js';
|
|
4
|
+
|
|
5
|
+
interface CertArgs {
|
|
6
|
+
query: string;
|
|
7
|
+
jurisdiction?: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function handleSearchCertificationGuidance(db: Database, args: CertArgs) {
|
|
11
|
+
const jv = validateJurisdiction(args.jurisdiction);
|
|
12
|
+
if (!jv.valid) return jv.error;
|
|
13
|
+
|
|
14
|
+
const searchTerm = `%${args.query}%`;
|
|
15
|
+
const rows = db.all<{
|
|
16
|
+
id: number;
|
|
17
|
+
step: string;
|
|
18
|
+
description: string;
|
|
19
|
+
inspector: string;
|
|
20
|
+
frequency: string;
|
|
21
|
+
cost_notes: string;
|
|
22
|
+
}>(
|
|
23
|
+
`SELECT * FROM certification_guidance WHERE jurisdiction = ?
|
|
24
|
+
AND (LOWER(step) LIKE LOWER(?) OR LOWER(description) LIKE LOWER(?))`,
|
|
25
|
+
[jv.jurisdiction, searchTerm, searchTerm]
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
return {
|
|
29
|
+
query: args.query,
|
|
30
|
+
jurisdiction: jv.jurisdiction,
|
|
31
|
+
results_count: rows.length,
|
|
32
|
+
results: rows.map(r => ({
|
|
33
|
+
step: r.step,
|
|
34
|
+
description: r.description,
|
|
35
|
+
inspector: r.inspector,
|
|
36
|
+
frequency: r.frequency,
|
|
37
|
+
cost_notes: r.cost_notes,
|
|
38
|
+
})),
|
|
39
|
+
_meta: buildMeta({ source_url: 'https://www.bio-inspecta.ch' }),
|
|
40
|
+
};
|
|
41
|
+
}
|