pinescript-mcp 0.7.2__tar.gz → 0.7.4__tar.gz
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.
- {pinescript_mcp-0.7.2 → pinescript_mcp-0.7.4}/PKG-INFO +2 -2
- {pinescript_mcp-0.7.2 → pinescript_mcp-0.7.4}/README.md +1 -1
- {pinescript_mcp-0.7.2 → pinescript_mcp-0.7.4}/pyproject.toml +1 -1
- {pinescript_mcp-0.7.2 → pinescript_mcp-0.7.4}/src/pinescript_mcp/__init__.py +1 -1
- {pinescript_mcp-0.7.2 → pinescript_mcp-0.7.4}/src/pinescript_mcp/server.py +74 -11
- {pinescript_mcp-0.7.2 → pinescript_mcp-0.7.4}/.gitignore +0 -0
- {pinescript_mcp-0.7.2 → pinescript_mcp-0.7.4}/LICENSE +0 -0
- {pinescript_mcp-0.7.2 → pinescript_mcp-0.7.4}/src/pinescript_mcp/__main__.py +0 -0
- {pinescript_mcp-0.7.2 → pinescript_mcp-0.7.4}/src/pinescript_mcp/docs/LLM_MANIFEST.md +0 -0
- {pinescript_mcp-0.7.2 → pinescript_mcp-0.7.4}/src/pinescript_mcp/docs/concepts/colors_and_display.md +0 -0
- {pinescript_mcp-0.7.2 → pinescript_mcp-0.7.4}/src/pinescript_mcp/docs/concepts/common_errors.md +0 -0
- {pinescript_mcp-0.7.2 → pinescript_mcp-0.7.4}/src/pinescript_mcp/docs/concepts/execution_model.md +0 -0
- {pinescript_mcp-0.7.2 → pinescript_mcp-0.7.4}/src/pinescript_mcp/docs/concepts/methods.md +0 -0
- {pinescript_mcp-0.7.2 → pinescript_mcp-0.7.4}/src/pinescript_mcp/docs/concepts/objects.md +0 -0
- {pinescript_mcp-0.7.2 → pinescript_mcp-0.7.4}/src/pinescript_mcp/docs/concepts/timeframes.md +0 -0
- {pinescript_mcp-0.7.2 → pinescript_mcp-0.7.4}/src/pinescript_mcp/docs/pine_v6_functions.json +0 -0
- {pinescript_mcp-0.7.2 → pinescript_mcp-0.7.4}/src/pinescript_mcp/docs/reference/annotations.md +0 -0
- {pinescript_mcp-0.7.2 → pinescript_mcp-0.7.4}/src/pinescript_mcp/docs/reference/constants.md +0 -0
- {pinescript_mcp-0.7.2 → pinescript_mcp-0.7.4}/src/pinescript_mcp/docs/reference/functions/collections.md +0 -0
- {pinescript_mcp-0.7.2 → pinescript_mcp-0.7.4}/src/pinescript_mcp/docs/reference/functions/drawing.md +0 -0
- {pinescript_mcp-0.7.2 → pinescript_mcp-0.7.4}/src/pinescript_mcp/docs/reference/functions/general.md +0 -0
- {pinescript_mcp-0.7.2 → pinescript_mcp-0.7.4}/src/pinescript_mcp/docs/reference/functions/request.md +0 -0
- {pinescript_mcp-0.7.2 → pinescript_mcp-0.7.4}/src/pinescript_mcp/docs/reference/functions/strategy.md +0 -0
- {pinescript_mcp-0.7.2 → pinescript_mcp-0.7.4}/src/pinescript_mcp/docs/reference/functions/ta.md +0 -0
- {pinescript_mcp-0.7.2 → pinescript_mcp-0.7.4}/src/pinescript_mcp/docs/reference/keywords.md +0 -0
- {pinescript_mcp-0.7.2 → pinescript_mcp-0.7.4}/src/pinescript_mcp/docs/reference/migration_v5_to_v6.md +0 -0
- {pinescript_mcp-0.7.2 → pinescript_mcp-0.7.4}/src/pinescript_mcp/docs/reference/operators.md +0 -0
- {pinescript_mcp-0.7.2 → pinescript_mcp-0.7.4}/src/pinescript_mcp/docs/reference/pine_v6_cheatsheet.md +0 -0
- {pinescript_mcp-0.7.2 → pinescript_mcp-0.7.4}/src/pinescript_mcp/docs/reference/pine_v6_functions.json +0 -0
- {pinescript_mcp-0.7.2 → pinescript_mcp-0.7.4}/src/pinescript_mcp/docs/reference/types.md +0 -0
- {pinescript_mcp-0.7.2 → pinescript_mcp-0.7.4}/src/pinescript_mcp/docs/reference/variables.md +0 -0
- {pinescript_mcp-0.7.2 → pinescript_mcp-0.7.4}/src/pinescript_mcp/docs/visuals/backgrounds.md +0 -0
- {pinescript_mcp-0.7.2 → pinescript_mcp-0.7.4}/src/pinescript_mcp/docs/visuals/bar_coloring.md +0 -0
- {pinescript_mcp-0.7.2 → pinescript_mcp-0.7.4}/src/pinescript_mcp/docs/visuals/bar_plotting.md +0 -0
- {pinescript_mcp-0.7.2 → pinescript_mcp-0.7.4}/src/pinescript_mcp/docs/visuals/colors.md +0 -0
- {pinescript_mcp-0.7.2 → pinescript_mcp-0.7.4}/src/pinescript_mcp/docs/visuals/fills.md +0 -0
- {pinescript_mcp-0.7.2 → pinescript_mcp-0.7.4}/src/pinescript_mcp/docs/visuals/levels.md +0 -0
- {pinescript_mcp-0.7.2 → pinescript_mcp-0.7.4}/src/pinescript_mcp/docs/visuals/lines_and_boxes.md +0 -0
- {pinescript_mcp-0.7.2 → pinescript_mcp-0.7.4}/src/pinescript_mcp/docs/visuals/overview.md +0 -0
- {pinescript_mcp-0.7.2 → pinescript_mcp-0.7.4}/src/pinescript_mcp/docs/visuals/plots.md +0 -0
- {pinescript_mcp-0.7.2 → pinescript_mcp-0.7.4}/src/pinescript_mcp/docs/visuals/tables.md +0 -0
- {pinescript_mcp-0.7.2 → pinescript_mcp-0.7.4}/src/pinescript_mcp/docs/visuals/texts_and_shapes.md +0 -0
- {pinescript_mcp-0.7.2 → pinescript_mcp-0.7.4}/src/pinescript_mcp/docs/writing_scripts/debugging.md +0 -0
- {pinescript_mcp-0.7.2 → pinescript_mcp-0.7.4}/src/pinescript_mcp/docs/writing_scripts/limitations.md +0 -0
- {pinescript_mcp-0.7.2 → pinescript_mcp-0.7.4}/src/pinescript_mcp/docs/writing_scripts/profiling_and_optimization.md +0 -0
- {pinescript_mcp-0.7.2 → pinescript_mcp-0.7.4}/src/pinescript_mcp/docs/writing_scripts/style_guide.md +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pinescript-mcp
|
|
3
|
-
Version: 0.7.
|
|
3
|
+
Version: 0.7.4
|
|
4
4
|
Summary: MCP server providing Pine Script v6 documentation for AI assistants
|
|
5
5
|
Project-URL: Homepage, https://github.com/paulieb89/pinescript-mcp
|
|
6
6
|
Project-URL: Documentation, https://github.com/paulieb89/pinescript-mcp#readme
|
|
@@ -121,7 +121,7 @@ Documentation is bundled in the package — each version contains a frozen snaps
|
|
|
121
121
|
"mcpServers": {
|
|
122
122
|
"pinescript-docs": {
|
|
123
123
|
"command": "uvx",
|
|
124
|
-
"args": ["pinescript-mcp==0.7.
|
|
124
|
+
"args": ["pinescript-mcp==0.7.4"]
|
|
125
125
|
}
|
|
126
126
|
}
|
|
127
127
|
}
|
|
@@ -11,6 +11,7 @@ from pathlib import Path
|
|
|
11
11
|
from typing import Literal
|
|
12
12
|
|
|
13
13
|
from fastmcp import FastMCP, Context
|
|
14
|
+
from fastmcp.exceptions import ToolError
|
|
14
15
|
from fastmcp.server.context import _current_transport
|
|
15
16
|
from fastmcp.server.middleware.logging import StructuredLoggingMiddleware
|
|
16
17
|
from fastmcp.server.middleware.rate_limiting import RateLimitingMiddleware
|
|
@@ -325,6 +326,15 @@ TOPIC_MAP = {
|
|
|
325
326
|
"migration": "reference/migration_v5_to_v6.md",
|
|
326
327
|
}
|
|
327
328
|
|
|
329
|
+
# Known invalid/renamed functions → specific replacement hints for validate_function()
|
|
330
|
+
KNOWN_REPLACEMENTS: dict[str, str] = {
|
|
331
|
+
"ta.adx": "ta.adx() does NOT exist. Use ta.dmi(diLen, adxSmoothing) → returns [diPlus, diMinus, adx] as a tuple.",
|
|
332
|
+
"ta.sum": "ta.sum() does NOT exist. Use math.sum(source, length) instead.",
|
|
333
|
+
"security": "security() was renamed in v5. Use request.security() instead.",
|
|
334
|
+
"study": "study() was renamed in v5. Use indicator() instead.",
|
|
335
|
+
"input": "input() works but prefer typed variants: input.int(), input.float(), input.string(), input.bool(), etc.",
|
|
336
|
+
}
|
|
337
|
+
|
|
328
338
|
|
|
329
339
|
def _find_section(content: str, header: str, include_children: bool = True) -> tuple[str, int, int]:
|
|
330
340
|
"""Find a section in markdown content by header text.
|
|
@@ -470,7 +480,7 @@ async def list_sections(path: str):
|
|
|
470
480
|
return "\n".join(headers)
|
|
471
481
|
except ValueError as e:
|
|
472
482
|
log["error"] = str(e)
|
|
473
|
-
|
|
483
|
+
raise ToolError(str(e))
|
|
474
484
|
|
|
475
485
|
|
|
476
486
|
@mcp.tool(
|
|
@@ -503,7 +513,7 @@ async def get_doc(path: str, limit: int = 0, offset: int = 0):
|
|
|
503
513
|
|
|
504
514
|
if limit > 0:
|
|
505
515
|
if offset >= total:
|
|
506
|
-
|
|
516
|
+
raise ToolError(f"offset {offset} exceeds file size ({total} chars). Use offset < {total}.")
|
|
507
517
|
end = min(offset + limit, total)
|
|
508
518
|
content = content[offset:end]
|
|
509
519
|
has_more = end < total
|
|
@@ -515,7 +525,7 @@ async def get_doc(path: str, limit: int = 0, offset: int = 0):
|
|
|
515
525
|
return content
|
|
516
526
|
except ValueError as e:
|
|
517
527
|
log["error"] = str(e)
|
|
518
|
-
|
|
528
|
+
raise ToolError(str(e))
|
|
519
529
|
|
|
520
530
|
|
|
521
531
|
@mcp.tool(
|
|
@@ -543,7 +553,7 @@ async def get_section(path: str, header: str, include_children: bool = True):
|
|
|
543
553
|
return f"# {path} (lines {start_line}-{end_line})\n\n{section}"
|
|
544
554
|
except ValueError as e:
|
|
545
555
|
log["error"] = str(e)
|
|
546
|
-
|
|
556
|
+
raise ToolError(str(e))
|
|
547
557
|
|
|
548
558
|
|
|
549
559
|
@mcp.tool(
|
|
@@ -556,14 +566,19 @@ async def search_docs(query: str, max_results: int = 5):
|
|
|
556
566
|
Finds sections containing the query and returns previews with
|
|
557
567
|
get_section() call hints so you can read the full content.
|
|
558
568
|
|
|
569
|
+
Multi-word queries use AND logic: all terms must appear in the
|
|
570
|
+
section (not necessarily on the same line).
|
|
571
|
+
|
|
559
572
|
Args:
|
|
560
|
-
query:
|
|
573
|
+
query: Search terms (case-insensitive). Multi-word queries
|
|
574
|
+
match sections containing ALL terms.
|
|
561
575
|
max_results: Maximum sections to return (default: 5)
|
|
562
576
|
|
|
563
577
|
Returns matching sections ranked by relevance with get_section() hints.
|
|
564
578
|
"""
|
|
565
579
|
with _timed_tool("search_docs", query=query, max_results=max_results) as log:
|
|
566
|
-
|
|
580
|
+
tokens = query.strip().split()
|
|
581
|
+
patterns = [re.compile(re.escape(t), re.IGNORECASE) for t in tokens]
|
|
567
582
|
section_hits = []
|
|
568
583
|
|
|
569
584
|
for rel_path in DOCS.keys():
|
|
@@ -580,8 +595,9 @@ async def search_docs(query: str, max_results: int = 5):
|
|
|
580
595
|
if header_match:
|
|
581
596
|
# Close previous section — check for hits
|
|
582
597
|
section_lines = lines[current_start:i]
|
|
583
|
-
|
|
584
|
-
if
|
|
598
|
+
section_text = "\n".join(section_lines)
|
|
599
|
+
if all(p.search(section_text) for p in patterns) and current_header != "(preamble)":
|
|
600
|
+
match_count = sum(sum(1 for p in patterns if p.search(l)) for l in section_lines)
|
|
585
601
|
section_hits.append({
|
|
586
602
|
"file": rel_path,
|
|
587
603
|
"header": current_header,
|
|
@@ -596,8 +612,9 @@ async def search_docs(query: str, max_results: int = 5):
|
|
|
596
612
|
|
|
597
613
|
# Final section
|
|
598
614
|
section_lines = lines[current_start:]
|
|
599
|
-
|
|
600
|
-
if
|
|
615
|
+
section_text = "\n".join(section_lines)
|
|
616
|
+
if all(p.search(section_text) for p in patterns) and current_header != "(preamble)":
|
|
617
|
+
match_count = sum(sum(1 for p in patterns if p.search(l)) for l in section_lines)
|
|
601
618
|
section_hits.append({
|
|
602
619
|
"file": rel_path,
|
|
603
620
|
"header": current_header,
|
|
@@ -687,6 +704,10 @@ async def validate_function(fn_name: str) -> ValidationResult:
|
|
|
687
704
|
if fn_name in PINE_V6_TOPLEVEL:
|
|
688
705
|
return ValidationResult(valid=True, type="toplevel", function=fn_name)
|
|
689
706
|
|
|
707
|
+
# Check known invalid/renamed functions before generic fallback
|
|
708
|
+
if fn_name in KNOWN_REPLACEMENTS:
|
|
709
|
+
return ValidationResult(valid=False, type=None, function=fn_name, suggestion=KNOWN_REPLACEMENTS[fn_name])
|
|
710
|
+
|
|
690
711
|
if "." in fn_name:
|
|
691
712
|
ns = fn_name.rpartition(".")[0]
|
|
692
713
|
if ns in PINE_V6_NAMESPACES:
|
|
@@ -738,6 +759,16 @@ async def resolve_topic(query: str) -> ResolveResult:
|
|
|
738
759
|
|
|
739
760
|
log["matches_found"] = len(path_scores)
|
|
740
761
|
|
|
762
|
+
if not path_scores:
|
|
763
|
+
# Fallback: scan docs for an exact substring match before returning empty
|
|
764
|
+
fallback_pattern = re.compile(re.escape(query), re.IGNORECASE)
|
|
765
|
+
for rel_path in DOCS:
|
|
766
|
+
doc_lines = _get_doc_lines(rel_path)
|
|
767
|
+
if doc_lines and any(fallback_pattern.search(l) for l in doc_lines):
|
|
768
|
+
path_scores[rel_path] = [query]
|
|
769
|
+
break
|
|
770
|
+
log["fallback_used"] = bool(path_scores)
|
|
771
|
+
|
|
741
772
|
if not path_scores:
|
|
742
773
|
return ResolveResult(
|
|
743
774
|
matches=[],
|
|
@@ -930,6 +961,31 @@ async def metrics(request):
|
|
|
930
961
|
return Response(generate_latest(METRICS_REGISTRY), media_type=CONTENT_TYPE_LATEST)
|
|
931
962
|
|
|
932
963
|
|
|
964
|
+
class _AcceptNormalizer:
|
|
965
|
+
"""Normalize Accept header on /mcp to prevent 406 Not Acceptable.
|
|
966
|
+
|
|
967
|
+
Workaround for modelcontextprotocol/python-sdk#2349 — the MCP SDK
|
|
968
|
+
requires both application/json AND text/event-stream in the Accept
|
|
969
|
+
header (even in SSE mode), but Anthropic's MCP proxy and other
|
|
970
|
+
clients send them separately per request type. Stamp the combined
|
|
971
|
+
value on /mcp only; SSE paths (/sse, /messages) pass through.
|
|
972
|
+
"""
|
|
973
|
+
def __init__(self, app, mcp_path: str = "/mcp"):
|
|
974
|
+
self.app = app
|
|
975
|
+
self._mcp_path = mcp_path.rstrip("/")
|
|
976
|
+
|
|
977
|
+
async def __call__(self, scope, receive, send):
|
|
978
|
+
if scope.get("type") == "http" and scope.get("path", "").rstrip("/") == self._mcp_path:
|
|
979
|
+
headers = [
|
|
980
|
+
(b"accept", b"application/json, text/event-stream")
|
|
981
|
+
if name.lower() == b"accept"
|
|
982
|
+
else (name, value)
|
|
983
|
+
for name, value in scope.get("headers", [])
|
|
984
|
+
]
|
|
985
|
+
scope = {**scope, "headers": headers}
|
|
986
|
+
await self.app(scope, receive, send)
|
|
987
|
+
|
|
988
|
+
|
|
933
989
|
def main():
|
|
934
990
|
"""Entry point for the CLI."""
|
|
935
991
|
import argparse
|
|
@@ -965,7 +1021,14 @@ def main():
|
|
|
965
1021
|
else:
|
|
966
1022
|
await streamable_app(scope, receive, send)
|
|
967
1023
|
|
|
968
|
-
config = uvicorn.Config(
|
|
1024
|
+
config = uvicorn.Config(
|
|
1025
|
+
_AcceptNormalizer(app),
|
|
1026
|
+
host=args.host,
|
|
1027
|
+
port=args.port,
|
|
1028
|
+
log_level="info",
|
|
1029
|
+
forwarded_allow_ips="*",
|
|
1030
|
+
proxy_headers=True,
|
|
1031
|
+
)
|
|
969
1032
|
server = uvicorn.Server(config)
|
|
970
1033
|
asyncio.run(server.serve())
|
|
971
1034
|
else:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pinescript_mcp-0.7.2 → pinescript_mcp-0.7.4}/src/pinescript_mcp/docs/concepts/colors_and_display.md
RENAMED
|
File without changes
|
{pinescript_mcp-0.7.2 → pinescript_mcp-0.7.4}/src/pinescript_mcp/docs/concepts/common_errors.md
RENAMED
|
File without changes
|
{pinescript_mcp-0.7.2 → pinescript_mcp-0.7.4}/src/pinescript_mcp/docs/concepts/execution_model.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pinescript_mcp-0.7.2 → pinescript_mcp-0.7.4}/src/pinescript_mcp/docs/concepts/timeframes.md
RENAMED
|
File without changes
|
{pinescript_mcp-0.7.2 → pinescript_mcp-0.7.4}/src/pinescript_mcp/docs/pine_v6_functions.json
RENAMED
|
File without changes
|
{pinescript_mcp-0.7.2 → pinescript_mcp-0.7.4}/src/pinescript_mcp/docs/reference/annotations.md
RENAMED
|
File without changes
|
{pinescript_mcp-0.7.2 → pinescript_mcp-0.7.4}/src/pinescript_mcp/docs/reference/constants.md
RENAMED
|
File without changes
|
|
File without changes
|
{pinescript_mcp-0.7.2 → pinescript_mcp-0.7.4}/src/pinescript_mcp/docs/reference/functions/drawing.md
RENAMED
|
File without changes
|
{pinescript_mcp-0.7.2 → pinescript_mcp-0.7.4}/src/pinescript_mcp/docs/reference/functions/general.md
RENAMED
|
File without changes
|
{pinescript_mcp-0.7.2 → pinescript_mcp-0.7.4}/src/pinescript_mcp/docs/reference/functions/request.md
RENAMED
|
File without changes
|
|
File without changes
|
{pinescript_mcp-0.7.2 → pinescript_mcp-0.7.4}/src/pinescript_mcp/docs/reference/functions/ta.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pinescript_mcp-0.7.2 → pinescript_mcp-0.7.4}/src/pinescript_mcp/docs/reference/operators.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pinescript_mcp-0.7.2 → pinescript_mcp-0.7.4}/src/pinescript_mcp/docs/reference/variables.md
RENAMED
|
File without changes
|
{pinescript_mcp-0.7.2 → pinescript_mcp-0.7.4}/src/pinescript_mcp/docs/visuals/backgrounds.md
RENAMED
|
File without changes
|
{pinescript_mcp-0.7.2 → pinescript_mcp-0.7.4}/src/pinescript_mcp/docs/visuals/bar_coloring.md
RENAMED
|
File without changes
|
{pinescript_mcp-0.7.2 → pinescript_mcp-0.7.4}/src/pinescript_mcp/docs/visuals/bar_plotting.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pinescript_mcp-0.7.2 → pinescript_mcp-0.7.4}/src/pinescript_mcp/docs/visuals/lines_and_boxes.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pinescript_mcp-0.7.2 → pinescript_mcp-0.7.4}/src/pinescript_mcp/docs/visuals/texts_and_shapes.md
RENAMED
|
File without changes
|
{pinescript_mcp-0.7.2 → pinescript_mcp-0.7.4}/src/pinescript_mcp/docs/writing_scripts/debugging.md
RENAMED
|
File without changes
|
{pinescript_mcp-0.7.2 → pinescript_mcp-0.7.4}/src/pinescript_mcp/docs/writing_scripts/limitations.md
RENAMED
|
File without changes
|
|
File without changes
|
{pinescript_mcp-0.7.2 → pinescript_mcp-0.7.4}/src/pinescript_mcp/docs/writing_scripts/style_guide.md
RENAMED
|
File without changes
|