note-connector 0.2.5 → 0.2.6

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.
Files changed (46) hide show
  1. package/dist/paths.js +4 -0
  2. package/dist/setup-dependencies.js +56 -13
  3. package/package.json +3 -2
  4. package/py/pyproject.toml +86 -0
  5. package/py/src/note_mcp/__init__.py +7 -0
  6. package/py/src/note_mcp/__main__.py +65 -0
  7. package/py/src/note_mcp/api/__init__.py +31 -0
  8. package/py/src/note_mcp/api/articles.py +1395 -0
  9. package/py/src/note_mcp/api/client.py +318 -0
  10. package/py/src/note_mcp/api/embeds.py +482 -0
  11. package/py/src/note_mcp/api/images.py +456 -0
  12. package/py/src/note_mcp/api/preview.py +142 -0
  13. package/py/src/note_mcp/api/public_notes.py +150 -0
  14. package/py/src/note_mcp/auth/__init__.py +9 -0
  15. package/py/src/note_mcp/auth/browser.py +574 -0
  16. package/py/src/note_mcp/auth/file_session.py +145 -0
  17. package/py/src/note_mcp/auth/session.py +240 -0
  18. package/py/src/note_mcp/browser/__init__.py +10 -0
  19. package/py/src/note_mcp/browser/config.py +21 -0
  20. package/py/src/note_mcp/browser/manager.py +182 -0
  21. package/py/src/note_mcp/browser/preview.py +68 -0
  22. package/py/src/note_mcp/browser/url_helpers.py +18 -0
  23. package/py/src/note_mcp/chatgpt/__init__.py +1 -0
  24. package/py/src/note_mcp/chatgpt/__main__.py +63 -0
  25. package/py/src/note_mcp/chatgpt/access_log.py +25 -0
  26. package/py/src/note_mcp/chatgpt/auth.py +52 -0
  27. package/py/src/note_mcp/chatgpt/images.py +92 -0
  28. package/py/src/note_mcp/chatgpt/login_once.py +26 -0
  29. package/py/src/note_mcp/chatgpt/middleware.py +31 -0
  30. package/py/src/note_mcp/chatgpt/tools.py +255 -0
  31. package/py/src/note_mcp/chatgpt/widgets.py +121 -0
  32. package/py/src/note_mcp/decorators.py +113 -0
  33. package/py/src/note_mcp/investigator/__init__.py +33 -0
  34. package/py/src/note_mcp/investigator/__main__.py +11 -0
  35. package/py/src/note_mcp/investigator/cli.py +313 -0
  36. package/py/src/note_mcp/investigator/core.py +653 -0
  37. package/py/src/note_mcp/investigator/mcp_tools.py +225 -0
  38. package/py/src/note_mcp/models.py +557 -0
  39. package/py/src/note_mcp/py.typed +0 -0
  40. package/py/src/note_mcp/server.py +905 -0
  41. package/py/src/note_mcp/utils/__init__.py +7 -0
  42. package/py/src/note_mcp/utils/file_parser.py +314 -0
  43. package/py/src/note_mcp/utils/html_to_markdown.py +477 -0
  44. package/py/src/note_mcp/utils/logging.py +119 -0
  45. package/py/src/note_mcp/utils/markdown.py +12 -0
  46. package/py/src/note_mcp/utils/markdown_to_html.py +826 -0
package/dist/paths.js CHANGED
@@ -40,6 +40,10 @@ export function resolveNoteConnectorRepo() {
40
40
  if (fs.existsSync(path.join(fromGlobal, "src", "note_mcp"))) {
41
41
  return fromGlobal;
42
42
  }
43
+ const fromBundled = path.resolve(here, "..", "py");
44
+ if (fs.existsSync(path.join(fromBundled, "src", "note_mcp"))) {
45
+ return fromBundled;
46
+ }
43
47
  throw new Error("note-connector Python 本体が見つかりません。\n"
44
48
  + " note-connector config set repoPath /path/to/note-connector\n"
45
49
  + "または NOTE_CONNECTOR_REPO=/path/to/note-connector を設定してください。\n"
@@ -39,23 +39,52 @@ function ensureUv() {
39
39
  function repoHasPython(root) {
40
40
  return fs.existsSync(path.join(root, "pyproject.toml")) && fs.existsSync(path.join(root, "src", "note_mcp"));
41
41
  }
42
- function cloneRepo(target) {
43
- if (!commandExists("git")) {
44
- throw new Error("git が見つかりません。git をインストールするか、\n" +
45
- "手動で clone して repoPath を設定してください。\n" +
46
- ` git clone ${DEFAULT_REPO} ${target}\n` +
47
- ` note-connector config set repoPath ${target}`);
42
+ function npmPackagePythonRoot() {
43
+ try {
44
+ const root = resolveNoteConnectorRepo();
45
+ return root;
46
+ }
47
+ catch {
48
+ return null;
49
+ }
50
+ }
51
+ function copyBundledPython(target) {
52
+ const source = npmPackagePythonRoot();
53
+ if (!source) {
54
+ throw new Error("npm パッケージにPythonソースが見つかりません。npm install -g を再実行してください。");
55
+ }
56
+ // Remove old repo and copy fresh from npm package
57
+ if (fs.existsSync(target)) {
58
+ fs.rmSync(target, { recursive: true, force: true });
48
59
  }
49
- console.log(`Cloning note-connector → ${target}`);
50
60
  fs.mkdirSync(path.dirname(target), { recursive: true });
51
- execFileSync("git", ["clone", "--depth", "1", DEFAULT_REPO, target], { stdio: "inherit" });
61
+ console.log("Python ソースを npm からコピー中…");
62
+ fs.cpSync(source, target, { recursive: true });
52
63
  }
53
- function updateRepo(repo) {
64
+ function updateRepo(target) {
65
+ // Prefer bundled npm copy over git pull
66
+ const npmRoot = npmPackagePythonRoot();
67
+ if (npmRoot && target !== npmRoot) {
68
+ try {
69
+ // Check which is newer: npm package vs local repo
70
+ const npmSrcStat = fs.statSync(path.join(npmRoot, "src", "note_mcp", "server.py"));
71
+ const localSrcStat = fs.statSync(path.join(target, "src", "note_mcp", "server.py"));
72
+ if (npmSrcStat.mtimeMs > localSrcStat.mtimeMs) {
73
+ copyBundledPython(target);
74
+ console.log("note-connector を最新に更新しました(npm同梱版)");
75
+ return;
76
+ }
77
+ }
78
+ catch {
79
+ // File stat failed, skip update
80
+ }
81
+ }
82
+ // Fall back to git pull for manually cloned repos
54
83
  if (!commandExists("git"))
55
84
  return;
56
85
  try {
57
86
  const result = spawnSync("git", ["pull", "--ff-only", "origin", "main"], {
58
- cwd: repo,
87
+ cwd: target,
59
88
  encoding: "utf8",
60
89
  timeout: 15000,
61
90
  });
@@ -80,9 +109,23 @@ function ensureRepoPath(config) {
80
109
  const target = path.join(configDir(), "repo");
81
110
  if (!repoHasPython(target)) {
82
111
  if (fs.existsSync(target)) {
83
- throw new Error(`Incomplete repo at ${target}. Remove it or set repoPath.`);
112
+ // Incomplete, remove and retry
113
+ fs.rmSync(target, { recursive: true, force: true });
114
+ }
115
+ // Try to copy from npm package first, fall back to git clone
116
+ const npmRoot = npmPackagePythonRoot();
117
+ if (npmRoot) {
118
+ copyBundledPython(target);
119
+ }
120
+ else if (commandExists("git")) {
121
+ console.log(`Cloning note-connector → ${target}`);
122
+ execFileSync("git", ["clone", "--depth", "1", DEFAULT_REPO, target], { stdio: "inherit" });
123
+ }
124
+ else {
125
+ throw new Error("Python ソースが見つからず、git もありません。\n" +
126
+ "npm install -g note-connector を再実行するか、\n" +
127
+ "git clone でリポジトリを取得して repoPath を設定してください。");
84
128
  }
85
- cloneRepo(target);
86
129
  }
87
130
  const updated = { ...config, repoPath: target };
88
131
  saveConfig(updated);
@@ -120,7 +163,7 @@ export async function setupDependencies() {
120
163
  warnings.push("Tailscale CLI がありません。tunnel.publicUrl を設定するか Tailscale をインストールしてください。");
121
164
  }
122
165
  if (!commandExists("git")) {
123
- warnings.push("git がありません。初回起動は動作しますが、更新を自動取得するには git をインストールしてください。");
166
+ warnings.push("git がありません。最新バージョンは npm install -g note-connector で更新されます。");
124
167
  }
125
168
  return {
126
169
  uvInstalled: true,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "note-connector",
3
- "version": "0.2.5",
3
+ "version": "0.2.6",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -12,13 +12,14 @@
12
12
  },
13
13
  "files": [
14
14
  "dist/**",
15
+ "py/**",
15
16
  "README.md"
16
17
  ],
17
18
  "engines": {
18
19
  "node": ">=20.10"
19
20
  },
20
21
  "scripts": {
21
- "build": "tsc -p tsconfig.json",
22
+ "build": "tsc -p tsconfig.json && mkdir -p py && cp -r ../src ../pyproject.toml py/ && find py -name __pycache__ -type d -exec rm -rf {} + 2>/dev/null; true",
22
23
  "test": "npm run build && node --test dist/net.test.js dist/version.test.js dist/config.test.js",
23
24
  "prepublishOnly": "npm run build"
24
25
  },
@@ -0,0 +1,86 @@
1
+ [project]
2
+ name = "note-connector"
3
+ version = "0.2.6"
4
+ description = "note-connector: MCP server and ChatGPT connector for note.com"
5
+ readme = "README.md"
6
+ requires-python = ">=3.13"
7
+ license = { text = "MIT" }
8
+ keywords = ["mcp", "note.com", "blog", "automation"]
9
+ classifiers = [
10
+ "Development Status :: 4 - Beta",
11
+ "Intended Audience :: Developers",
12
+ "License :: OSI Approved :: MIT License",
13
+ "Programming Language :: Python :: 3",
14
+ "Programming Language :: Python :: 3.13",
15
+ ]
16
+ dependencies = [
17
+ "fastmcp>=2.0.0",
18
+ "playwright>=1.40.0",
19
+ "keyring>=25.0.0",
20
+ "httpx>=0.27.0",
21
+ "markdown-it-py>=3.0.0",
22
+ "pydantic>=2.0.0",
23
+ "pyyaml>=6.0.0",
24
+ "uvicorn>=0.40.0",
25
+ ]
26
+
27
+ [dependency-groups]
28
+ dev = [
29
+ "beautifulsoup4>=4.12.0",
30
+ "mitmproxy>=11.0.2",
31
+ "mypy>=1.19.1",
32
+ "pytest>=8.4.1",
33
+ "pytest-asyncio>=0.23.0",
34
+ "ruff>=0.12.4",
35
+ "types-beautifulsoup4>=4.12.0",
36
+ "types-pyyaml>=6.0.0",
37
+ ]
38
+ docs = [
39
+ "myst-parser>=4.0.1",
40
+ "sphinx>=8.2.3",
41
+ "sphinx-rtd-theme>=3.0.2",
42
+ "sphinxcontrib-mermaid>=1.1.0",
43
+ ]
44
+
45
+ [build-system]
46
+ requires = ["hatchling"]
47
+ build-backend = "hatchling.build"
48
+
49
+ [tool.hatch.build.targets.wheel]
50
+ packages = ["src/note_mcp"]
51
+
52
+ [tool.ruff]
53
+ line-length = 120
54
+ target-version = "py313"
55
+ exclude = ["tests/manual"]
56
+
57
+ [tool.ruff.lint]
58
+ select = ["E", "F", "W", "I", "UP", "B", "C4", "SIM"]
59
+ ignore = []
60
+
61
+ [tool.ruff.lint.isort]
62
+ known-first-party = ["note_mcp"]
63
+
64
+ [tool.mypy]
65
+ python_version = "3.13"
66
+ strict = true
67
+ warn_return_any = true
68
+ warn_unused_ignores = true
69
+ disallow_untyped_defs = true
70
+ exclude = ["^debug/", "^tests/manual/"]
71
+
72
+ [[tool.mypy.overrides]]
73
+ module = "mitmproxy.*"
74
+ ignore_errors = true
75
+ follow_imports = "skip"
76
+
77
+
78
+ [tool.pytest.ini_options]
79
+ asyncio_mode = "auto"
80
+ testpaths = ["tests"]
81
+ markers = [
82
+ "requires_auth: tests requiring real authentication",
83
+ "e2e: end-to-end tests",
84
+ "docker: tests requiring Docker environment with browser/proxy",
85
+ "playwright: tests requiring Playwright browser automation",
86
+ ]
@@ -0,0 +1,7 @@
1
+ """note-mcp: MCP server for managing note.com articles."""
2
+
3
+ from note_mcp.server import mcp
4
+
5
+ __version__ = "0.1.0"
6
+
7
+ __all__ = ["__version__", "mcp"]
@@ -0,0 +1,65 @@
1
+ """Entry point for note-mcp MCP server.
2
+
3
+ Run with: uv run python -m note_mcp
4
+ Or via fastmcp: uv run fastmcp run note_mcp.server:mcp
5
+
6
+ Investigator mode (stdio):
7
+ uv run python -m note_mcp --investigator
8
+ Or set INVESTIGATOR_MODE=1 environment variable
9
+
10
+ Investigator mode (HTTP for remote access):
11
+ uv run python -m note_mcp --investigator --http --port 9000
12
+ This exposes MCP tools via HTTP transport for Claude Code on host.
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ import argparse
18
+ import os
19
+
20
+
21
+ def main() -> None:
22
+ """Run the MCP server."""
23
+ parser = argparse.ArgumentParser(description="note-mcp MCP server")
24
+ parser.add_argument(
25
+ "--investigator",
26
+ action="store_true",
27
+ help="Enable investigator mode for API investigation",
28
+ )
29
+ parser.add_argument(
30
+ "--http",
31
+ action="store_true",
32
+ help="Use HTTP transport instead of stdio (for remote access)",
33
+ )
34
+ parser.add_argument(
35
+ "--host",
36
+ type=str,
37
+ default="0.0.0.0",
38
+ help="Host to bind HTTP server (default: 0.0.0.0)",
39
+ )
40
+ parser.add_argument(
41
+ "--port",
42
+ type=int,
43
+ default=9000,
44
+ help="Port for HTTP transport (default: 9000)",
45
+ )
46
+ args = parser.parse_args()
47
+
48
+ if args.investigator:
49
+ os.environ["INVESTIGATOR_MODE"] = "1"
50
+
51
+ # Import server after setting environment variable
52
+ from note_mcp.server import mcp
53
+
54
+ if args.http:
55
+ # stateless_http=True to avoid session ID management issues
56
+ # when the server is restarted (e.g., container restart).
57
+ # Application-level session state (CaptureSessionManager) is
58
+ # independent of HTTP transport session management.
59
+ mcp.run(transport="http", host=args.host, port=args.port, stateless_http=True)
60
+ else:
61
+ mcp.run()
62
+
63
+
64
+ if __name__ == "__main__":
65
+ main()
@@ -0,0 +1,31 @@
1
+ """API module for note-mcp.
2
+
3
+ Provides HTTP client and API operations for note.com.
4
+ """
5
+
6
+ from note_mcp.api.articles import (
7
+ create_draft,
8
+ delete_all_drafts,
9
+ delete_article,
10
+ delete_draft,
11
+ list_articles,
12
+ publish_article,
13
+ unpublish_article,
14
+ update_article,
15
+ )
16
+ from note_mcp.api.client import NoteAPIClient
17
+ from note_mcp.api.images import upload_body_image, upload_eyecatch_image
18
+
19
+ __all__ = [
20
+ "NoteAPIClient",
21
+ "create_draft",
22
+ "delete_all_drafts",
23
+ "delete_article",
24
+ "delete_draft",
25
+ "list_articles",
26
+ "publish_article",
27
+ "unpublish_article",
28
+ "update_article",
29
+ "upload_body_image",
30
+ "upload_eyecatch_image",
31
+ ]