tengwar 0.3.1__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.
tengwar/mcp_server.py ADDED
@@ -0,0 +1,496 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ TENGWAR MCP Server
4
+
5
+ Model Context Protocol server that exposes Tengwar as a tool
6
+ to any MCP-compatible AI agent (Claude, etc).
7
+
8
+ Tools exposed:
9
+ - tengwar_eval: Execute Tengwar source code
10
+ - tengwar_eval_binary: Execute base64-encoded binary AST
11
+ - tengwar_sandbox: Execute in sandboxed mode (safe for untrusted code)
12
+
13
+ Resources exposed:
14
+ - tengwar://builtins: List of all available builtins
15
+ - tengwar://syntax: Quick syntax reference
16
+
17
+ Usage:
18
+ python -m tengwar.mcp_server # stdio mode
19
+ python -m tengwar.mcp_server --port 8080 # HTTP mode (if supported)
20
+
21
+ MCP config (claude_desktop_config.json):
22
+ {
23
+ "mcpServers": {
24
+ "tengwar": {
25
+ "command": "python",
26
+ "args": ["-m", "tengwar.mcp_server"]
27
+ }
28
+ }
29
+ }
30
+ """
31
+
32
+ import sys
33
+ import json
34
+ import traceback
35
+ from typing import Any
36
+
37
+ # Import Tengwar
38
+ from .interpreter import Interpreter, TengwarValue
39
+ from .binary_ast import run_b64, ASTBuilder
40
+
41
+
42
+ # === MCP Protocol Implementation (stdio JSON-RPC) ===
43
+
44
+ class TengwarMCPServer:
45
+ """MCP server exposing Tengwar as tools for AI agents."""
46
+
47
+ def __init__(self):
48
+ self.interpreter = Interpreter()
49
+ self.sandbox_interpreter = Interpreter(sandbox=True)
50
+ self.sandbox_interpreter.max_steps = 1_000_000
51
+
52
+ def handle_request(self, request: dict) -> dict:
53
+ """Handle a JSON-RPC request."""
54
+ method = request.get("method", "")
55
+ req_id = request.get("id")
56
+ params = request.get("params", {})
57
+
58
+ try:
59
+ if method == "initialize":
60
+ return self._initialize(req_id, params)
61
+ elif method == "tools/list":
62
+ return self._list_tools(req_id)
63
+ elif method == "tools/call":
64
+ return self._call_tool(req_id, params)
65
+ elif method == "resources/list":
66
+ return self._list_resources(req_id)
67
+ elif method == "resources/read":
68
+ return self._read_resource(req_id, params)
69
+ elif method == "ping":
70
+ return self._result(req_id, {})
71
+ elif method == "notifications/initialized":
72
+ return None # No response needed
73
+ else:
74
+ return self._error(req_id, -32601, f"Method not found: {method}")
75
+ except Exception as e:
76
+ return self._error(req_id, -32603, str(e))
77
+
78
+ def _result(self, req_id, result):
79
+ return {"jsonrpc": "2.0", "id": req_id, "result": result}
80
+
81
+ def _error(self, req_id, code, message):
82
+ return {"jsonrpc": "2.0", "id": req_id, "error": {"code": code, "message": message}}
83
+
84
+ def _initialize(self, req_id, params):
85
+ return self._result(req_id, {
86
+ "protocolVersion": "2024-11-05",
87
+ "capabilities": {
88
+ "tools": {},
89
+ "resources": {}
90
+ },
91
+ "serverInfo": {
92
+ "name": "tengwar",
93
+ "version": "0.3.0"
94
+ }
95
+ })
96
+
97
+ def _list_tools(self, req_id):
98
+ return self._result(req_id, {
99
+ "tools": [
100
+ {
101
+ "name": "tengwar_eval",
102
+ "description": (
103
+ "Execute Tengwar code. Tengwar is a functional language optimized for AI — "
104
+ "23% fewer tokens than Python for data operations, with 80+ builtins including "
105
+ "map, filter, reduce, scan, chunks, partition, unique, flat-map, pattern matching, "
106
+ "dictionaries, JSON, and Python interop. "
107
+ "Example: (sum (for x (range 1 11) when (even? x) (* x 2))) → 60"
108
+ ),
109
+ "inputSchema": {
110
+ "type": "object",
111
+ "properties": {
112
+ "code": {
113
+ "type": "string",
114
+ "description": "Tengwar source code to execute"
115
+ }
116
+ },
117
+ "required": ["code"]
118
+ }
119
+ },
120
+ {
121
+ "name": "tengwar_sandbox",
122
+ "description": (
123
+ "Execute Tengwar code in a secure sandbox. No file I/O, no network, "
124
+ "no Python imports, with a 1M step limit. Safe for untrusted code. "
125
+ "Pure computation only."
126
+ ),
127
+ "inputSchema": {
128
+ "type": "object",
129
+ "properties": {
130
+ "code": {
131
+ "type": "string",
132
+ "description": "Tengwar source code to execute (sandboxed)"
133
+ },
134
+ "max_steps": {
135
+ "type": "integer",
136
+ "description": "Maximum execution steps (default: 1000000)",
137
+ "default": 1000000
138
+ }
139
+ },
140
+ "required": ["code"]
141
+ }
142
+ },
143
+ {
144
+ "name": "tengwar_eval_binary",
145
+ "description": (
146
+ "Execute a base64-encoded Tengwar binary AST. "
147
+ "Zero syntax errors possible — the binary format maps directly to AST nodes. "
148
+ "Use tengwar_eval to generate base64 with: (encode-b64 \"(+ 1 2)\")"
149
+ ),
150
+ "inputSchema": {
151
+ "type": "object",
152
+ "properties": {
153
+ "b64": {
154
+ "type": "string",
155
+ "description": "Base64-encoded binary AST"
156
+ }
157
+ },
158
+ "required": ["b64"]
159
+ }
160
+ },
161
+ {
162
+ "name": "tengwar_multi",
163
+ "description": (
164
+ "Execute multiple Tengwar expressions sharing the same environment. "
165
+ "Define functions in earlier expressions, use them in later ones. "
166
+ "Returns the result of the last expression."
167
+ ),
168
+ "inputSchema": {
169
+ "type": "object",
170
+ "properties": {
171
+ "expressions": {
172
+ "type": "array",
173
+ "items": {"type": "string"},
174
+ "description": "Array of Tengwar expressions to execute in sequence"
175
+ }
176
+ },
177
+ "required": ["expressions"]
178
+ }
179
+ }
180
+ ]
181
+ })
182
+
183
+ def _call_tool(self, req_id, params):
184
+ tool_name = params.get("name", "")
185
+ args = params.get("arguments", {})
186
+
187
+ if tool_name == "tengwar_eval":
188
+ return self._eval(req_id, args.get("code", ""), sandbox=False)
189
+ elif tool_name == "tengwar_sandbox":
190
+ max_steps = args.get("max_steps", 1_000_000)
191
+ self.sandbox_interpreter.max_steps = max_steps
192
+ self.sandbox_interpreter.step_count = 0
193
+ return self._eval(req_id, args.get("code", ""), sandbox=True)
194
+ elif tool_name == "tengwar_eval_binary":
195
+ return self._eval_binary(req_id, args.get("b64", ""))
196
+ elif tool_name == "tengwar_multi":
197
+ return self._eval_multi(req_id, args.get("expressions", []))
198
+ else:
199
+ return self._error(req_id, -32602, f"Unknown tool: {tool_name}")
200
+
201
+ def _eval(self, req_id, code: str, sandbox: bool = False):
202
+ try:
203
+ interp = self.sandbox_interpreter if sandbox else self.interpreter
204
+ result = interp.run_source(code)
205
+ return self._result(req_id, {
206
+ "content": [{
207
+ "type": "text",
208
+ "text": repr(result)
209
+ }]
210
+ })
211
+ except Exception as e:
212
+ return self._result(req_id, {
213
+ "content": [{
214
+ "type": "text",
215
+ "text": f"Error: {e}"
216
+ }],
217
+ "isError": True
218
+ })
219
+
220
+ def _eval_binary(self, req_id, b64: str):
221
+ try:
222
+ result = run_b64(b64, self.interpreter)
223
+ return self._result(req_id, {
224
+ "content": [{
225
+ "type": "text",
226
+ "text": repr(result)
227
+ }]
228
+ })
229
+ except Exception as e:
230
+ return self._result(req_id, {
231
+ "content": [{
232
+ "type": "text",
233
+ "text": f"Error: {e}"
234
+ }],
235
+ "isError": True
236
+ })
237
+
238
+ def _eval_multi(self, req_id, expressions: list):
239
+ try:
240
+ result = None
241
+ for expr in expressions:
242
+ result = self.interpreter.run_source(expr)
243
+ return self._result(req_id, {
244
+ "content": [{
245
+ "type": "text",
246
+ "text": repr(result)
247
+ }]
248
+ })
249
+ except Exception as e:
250
+ return self._result(req_id, {
251
+ "content": [{
252
+ "type": "text",
253
+ "text": f"Error: {e}"
254
+ }],
255
+ "isError": True
256
+ })
257
+
258
+ def _list_resources(self, req_id):
259
+ return self._result(req_id, {
260
+ "resources": [
261
+ {
262
+ "uri": "tengwar://builtins",
263
+ "name": "Tengwar Builtins",
264
+ "description": "Complete list of all 80+ built-in functions",
265
+ "mimeType": "text/plain"
266
+ },
267
+ {
268
+ "uri": "tengwar://syntax",
269
+ "name": "Tengwar Syntax Reference",
270
+ "description": "Quick syntax guide for writing Tengwar code",
271
+ "mimeType": "text/plain"
272
+ }
273
+ ]
274
+ })
275
+
276
+ def _read_resource(self, req_id, params):
277
+ uri = params.get("uri", "")
278
+
279
+ if uri == "tengwar://builtins":
280
+ return self._result(req_id, {
281
+ "contents": [{
282
+ "uri": uri,
283
+ "mimeType": "text/plain",
284
+ "text": BUILTINS_REFERENCE
285
+ }]
286
+ })
287
+ elif uri == "tengwar://syntax":
288
+ return self._result(req_id, {
289
+ "contents": [{
290
+ "uri": uri,
291
+ "mimeType": "text/plain",
292
+ "text": SYNTAX_REFERENCE
293
+ }]
294
+ })
295
+ else:
296
+ return self._error(req_id, -32602, f"Unknown resource: {uri}")
297
+
298
+ def run_stdio(self):
299
+ """Run as stdio MCP server."""
300
+ for line in sys.stdin:
301
+ line = line.strip()
302
+ if not line:
303
+ continue
304
+ try:
305
+ request = json.loads(line)
306
+ response = self.handle_request(request)
307
+ if response is not None:
308
+ sys.stdout.write(json.dumps(response) + "\n")
309
+ sys.stdout.flush()
310
+ except json.JSONDecodeError:
311
+ error = {
312
+ "jsonrpc": "2.0",
313
+ "id": None,
314
+ "error": {"code": -32700, "message": "Parse error"}
315
+ }
316
+ sys.stdout.write(json.dumps(error) + "\n")
317
+ sys.stdout.flush()
318
+
319
+
320
+ # === Reference Content ===
321
+
322
+ BUILTINS_REFERENCE = """TENGWAR BUILTINS (v0.3)
323
+
324
+ COLLECTIONS:
325
+ (map f vec) Apply f to each element
326
+ (filter f vec) Keep elements where f returns true
327
+ (reduce f init vec) Fold left with accumulator
328
+ (flat-map f vec) Map then flatten one level
329
+ (find f vec) First element where f is true
330
+ (index-of f vec) Index of first match
331
+ (take n vec) First n elements
332
+ (drop n vec) Remove first n elements
333
+ (take-while f vec) Take while f is true
334
+ (drop-while f vec) Drop while f is true
335
+ (zip-with f v1 v2) Combine two vectors element-wise
336
+ (group-by f vec) Group by key function → dict
337
+ (unique vec) Remove duplicates
338
+ (frequencies vec) Count occurrences → dict
339
+ (partition f vec) Split into (matches, non-matches)
340
+ (scan f init vec) Running accumulation
341
+ (chunks n vec) Split into groups of n
342
+ (interleave v1 v2) Alternate elements
343
+ (repeat n val) Vector of n copies
344
+ (iterate f init n) Generate by repeated application
345
+ (sort vec) Sort ascending
346
+ (sort-by f vec) Sort by key function
347
+ (reverse vec) Reverse order
348
+ (concat v1 v2) Concatenate vectors
349
+ (len vec) Length
350
+ (head vec) First element
351
+ (tail vec) All but first
352
+ (last vec) Last element
353
+ (nth n vec) Element at index n
354
+ (range a b) Numbers from a to b-1
355
+ (sum vec) Sum all elements
356
+ (product vec) Multiply all elements
357
+ (any? f vec) True if any match
358
+ (all? f vec) True if all match
359
+ (count f vec) Count matches
360
+ (for-each f vec) Execute f for side effects
361
+
362
+ DICTIONARIES:
363
+ (dict k1 v1 k2 v2 ...) Create dictionary
364
+ (dict-get d key) Get value (optional default)
365
+ (dict-set d key val) Set key → new dict
366
+ (dict-del d key) Remove key → new dict
367
+ (dict-keys d) All keys as vector
368
+ (dict-vals d) All values as vector
369
+ (dict-pairs d) Key-value pairs as vector of tuples
370
+ (dict-has? d key) Key exists?
371
+ (dict-merge d1 d2) Merge two dicts
372
+ (dict-size d) Number of entries
373
+
374
+ STRINGS:
375
+ (fmt template args...) String interpolation: (fmt "Hi {}!" name)
376
+ (split str sep) Split string
377
+ (join sep vec) Join with separator
378
+ (upper str) Uppercase
379
+ (lower str) Lowercase
380
+ (trim str) Strip whitespace
381
+ (replace str old new) Replace substring
382
+ (starts-with? str pre) Prefix check
383
+ (ends-with? str suf) Suffix check
384
+ (chars str) String → vector of chars
385
+ (char-at str n) Character at index
386
+ (pad-left str n ch) Left pad to width
387
+ (pad-right str n ch) Right pad to width
388
+
389
+ MATH:
390
+ (abs x) Absolute value
391
+ (min a b ...) or (min v) Minimum
392
+ (max a b ...) or (max v) Maximum
393
+ (clamp x lo hi) Clamp to range
394
+ (floor x) Floor
395
+ (ceil x) Ceiling
396
+ (round x) Round
397
+ pi e inf nan Constants
398
+ (rand) Random float [0, 1)
399
+ (rand-int lo hi) Random int [lo, hi)
400
+
401
+ TYPE PREDICATES:
402
+ (int? x) (float? x) (num? x) (str? x) (bool? x)
403
+ (vec? x) (tuple? x) (dict? x) (fn? x) (nil? x) (py? x)
404
+ (type x) Type name as string
405
+ (zero? x) (pos? x) (neg? x) (even? x) (odd? x)
406
+ (divides? n d) n divisible by d?
407
+ (empty? x) Empty collection or string?
408
+
409
+ JSON:
410
+ (json-parse str) Parse JSON → Tengwar value
411
+ (json-encode val) Encode to JSON string
412
+
413
+ FILE I/O:
414
+ (read-file path) Read file contents
415
+ (write-file path str) Write string to file
416
+ (append-file path str) Append to file
417
+ (file-exists? path) Check if file exists
418
+
419
+ HTTP:
420
+ (http-get url) GET request → dict with status, headers, body
421
+ (http-post url body) POST request
422
+
423
+ PYTHON INTEROP:
424
+ (py-import "module") Import Python module
425
+ (py-call obj "method" args...) Call method
426
+ (py-attr obj "attr") Get attribute
427
+ (py-eval "expression") Evaluate Python expression
428
+ obj.attr Dot access on Python objects
429
+
430
+ SYSTEM:
431
+ (time-ms) Current time in milliseconds
432
+ (sleep ms) Sleep for milliseconds
433
+ (uuid) Generate UUID
434
+ (env-get name) Environment variable
435
+
436
+ COMPREHENSION:
437
+ (for x coll body) → (map (fn x body) coll)
438
+ (for x coll when pred body) → filter + map
439
+ """
440
+
441
+ SYNTAX_REFERENCE = """TENGWAR SYNTAX REFERENCE (v0.3)
442
+
443
+ LITERALS:
444
+ 42 3.14 "hello" true false nil
445
+
446
+ COLLECTIONS:
447
+ ⟦1 2 3⟧ or [1 2 3] Vector
448
+ ⟨1 2 3⟩ Tuple
449
+ (dict "a" 1 "b" 2) Dictionary
450
+
451
+ FUNCTIONS:
452
+ (λ x y (+ x y)) Lambda (or: fn x y (+ x y))
453
+ {+ _ 1} Short lambda (_ is parameter)
454
+ (defn name args... body) Define named function
455
+
456
+ CONTROL FLOW:
457
+ (? cond then else) Conditional (or: if)
458
+ (cond (t1 v1) (t2 v2) (_ default)) Multi-branch
459
+ (match expr (pat body) ...) Pattern matching
460
+
461
+ BINDINGS:
462
+ (≡ name value) Define (or: def)
463
+ (let x 1 y 2 (+ x y)) Local bindings
464
+ (>> e1 → name e2) Sequence + bind
465
+
466
+ ITERATION:
467
+ (for x coll body) Comprehension (map)
468
+ (for x coll when p body) Comprehension (filter + map)
469
+ (pipe val f1 f2 f3) Pipeline
470
+
471
+ RECURSION:
472
+ (↺ name (λ args body)) Recursive definition (or: rec)
473
+ (defn name args body) Named function (auto-recursive)
474
+
475
+ ERROR HANDLING:
476
+ (throw expr) Throw error
477
+ (catch expr handler) Catch error
478
+ (try thunk handler) Try/catch returning (ok?, result)
479
+
480
+ PARALLEL:
481
+ (‖ e1 e2 e3) Parallel execution (or: par)
482
+
483
+ PYTHON:
484
+ (py-import "module") Import
485
+ (py-call obj "method" args...)
486
+ obj.attr Dot access
487
+ """
488
+
489
+
490
+ def main():
491
+ server = TengwarMCPServer()
492
+ server.run_stdio()
493
+
494
+
495
+ if __name__ == "__main__":
496
+ main()