@contextstream/mcp-server 0.4.43 → 0.4.44
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/dist/index.js +727 -17
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -7691,7 +7691,8 @@ var ContextStreamClient = class {
|
|
|
7691
7691
|
...Array.isArray(data?.errors) ? { errors: data.errors } : {},
|
|
7692
7692
|
...Array.isArray(data?.warnings) && data.warnings.length > 0 ? { warnings: data.warnings } : {},
|
|
7693
7693
|
...this.indexRefreshInProgress ? { index_status: "refreshing" } : {},
|
|
7694
|
-
...data?.context_pressure ? { context_pressure: data.context_pressure } : {}
|
|
7694
|
+
...data?.context_pressure ? { context_pressure: data.context_pressure } : {},
|
|
7695
|
+
...data?.semantic_intent ? { semantic_intent: data.semantic_intent } : {}
|
|
7695
7696
|
};
|
|
7696
7697
|
} catch (err) {
|
|
7697
7698
|
const message2 = err instanceof Error ? err.message : String(err);
|
|
@@ -8831,6 +8832,145 @@ W:${wsHint}
|
|
|
8831
8832
|
uuidSchema.parse(params.task_id);
|
|
8832
8833
|
return request(this.config, `/tasks/${params.task_id}`, { method: "DELETE" });
|
|
8833
8834
|
}
|
|
8835
|
+
// ============================================================================
|
|
8836
|
+
// Media/Content Methods (for video, audio, image indexing)
|
|
8837
|
+
// ============================================================================
|
|
8838
|
+
/**
|
|
8839
|
+
* Initialize a media upload and get a presigned URL.
|
|
8840
|
+
* After calling this, upload the file to the returned upload_url with the specified headers.
|
|
8841
|
+
*/
|
|
8842
|
+
async mediaInitUpload(params) {
|
|
8843
|
+
const withDefaults = this.withDefaults(params);
|
|
8844
|
+
if (!withDefaults.workspace_id) {
|
|
8845
|
+
throw new Error("workspace_id is required for media upload");
|
|
8846
|
+
}
|
|
8847
|
+
const body = {
|
|
8848
|
+
filename: params.filename,
|
|
8849
|
+
size_bytes: params.size_bytes,
|
|
8850
|
+
content_type: params.content_type.toLowerCase(),
|
|
8851
|
+
// Backend uses snake_case (lowercase)
|
|
8852
|
+
mime_type: params.mime_type,
|
|
8853
|
+
title: params.title,
|
|
8854
|
+
tags: params.tags || []
|
|
8855
|
+
};
|
|
8856
|
+
const result = await request(
|
|
8857
|
+
this.config,
|
|
8858
|
+
`/workspaces/${withDefaults.workspace_id}/content/uploads/init`,
|
|
8859
|
+
{ method: "POST", body }
|
|
8860
|
+
);
|
|
8861
|
+
return unwrapApiResponse(result);
|
|
8862
|
+
}
|
|
8863
|
+
/**
|
|
8864
|
+
* Complete a media upload and trigger indexing.
|
|
8865
|
+
* Call this after successfully uploading the file to the presigned URL.
|
|
8866
|
+
*/
|
|
8867
|
+
async mediaCompleteUpload(params) {
|
|
8868
|
+
const withDefaults = this.withDefaults(params);
|
|
8869
|
+
if (!withDefaults.workspace_id) {
|
|
8870
|
+
throw new Error("workspace_id is required to complete upload");
|
|
8871
|
+
}
|
|
8872
|
+
uuidSchema.parse(params.content_id);
|
|
8873
|
+
const result = await request(
|
|
8874
|
+
this.config,
|
|
8875
|
+
`/workspaces/${withDefaults.workspace_id}/content/${params.content_id}/complete-upload`,
|
|
8876
|
+
{ method: "POST" }
|
|
8877
|
+
);
|
|
8878
|
+
return unwrapApiResponse(result);
|
|
8879
|
+
}
|
|
8880
|
+
/**
|
|
8881
|
+
* Get the status of a content item (for checking indexing progress).
|
|
8882
|
+
*/
|
|
8883
|
+
async mediaGetContent(params) {
|
|
8884
|
+
const withDefaults = this.withDefaults(params);
|
|
8885
|
+
if (!withDefaults.workspace_id) {
|
|
8886
|
+
throw new Error("workspace_id is required for getting content");
|
|
8887
|
+
}
|
|
8888
|
+
uuidSchema.parse(params.content_id);
|
|
8889
|
+
const result = await request(
|
|
8890
|
+
this.config,
|
|
8891
|
+
`/workspaces/${withDefaults.workspace_id}/content/${params.content_id}`,
|
|
8892
|
+
{ method: "GET" }
|
|
8893
|
+
);
|
|
8894
|
+
return unwrapApiResponse(result);
|
|
8895
|
+
}
|
|
8896
|
+
/**
|
|
8897
|
+
* List content items in a workspace.
|
|
8898
|
+
*/
|
|
8899
|
+
async mediaListContent(params) {
|
|
8900
|
+
const withDefaults = this.withDefaults(params || {});
|
|
8901
|
+
if (!withDefaults.workspace_id) {
|
|
8902
|
+
throw new Error("workspace_id is required for listing content");
|
|
8903
|
+
}
|
|
8904
|
+
const query = new URLSearchParams();
|
|
8905
|
+
if (params?.content_type) query.set("content_type", params.content_type);
|
|
8906
|
+
if (params?.status) query.set("status", params.status);
|
|
8907
|
+
if (params?.limit) query.set("limit", String(params.limit));
|
|
8908
|
+
if (params?.offset) query.set("offset", String(params.offset));
|
|
8909
|
+
const suffix = query.toString() ? `?${query.toString()}` : "";
|
|
8910
|
+
const result = await request(
|
|
8911
|
+
this.config,
|
|
8912
|
+
`/workspaces/${withDefaults.workspace_id}/content${suffix}`,
|
|
8913
|
+
{ method: "GET" }
|
|
8914
|
+
);
|
|
8915
|
+
return unwrapApiResponse(result);
|
|
8916
|
+
}
|
|
8917
|
+
/**
|
|
8918
|
+
* Search content by semantic query (transcripts, descriptions, etc.).
|
|
8919
|
+
*/
|
|
8920
|
+
async mediaSearchContent(params) {
|
|
8921
|
+
const withDefaults = this.withDefaults(params);
|
|
8922
|
+
if (!withDefaults.workspace_id) {
|
|
8923
|
+
throw new Error("workspace_id is required for searching content");
|
|
8924
|
+
}
|
|
8925
|
+
const query = new URLSearchParams();
|
|
8926
|
+
query.set("q", params.query);
|
|
8927
|
+
if (params.content_type) query.set("content_type", params.content_type);
|
|
8928
|
+
if (params.limit) query.set("limit", String(params.limit));
|
|
8929
|
+
if (params.offset) query.set("offset", String(params.offset));
|
|
8930
|
+
const result = await request(
|
|
8931
|
+
this.config,
|
|
8932
|
+
`/workspaces/${withDefaults.workspace_id}/content/search?${query.toString()}`,
|
|
8933
|
+
{ method: "GET" }
|
|
8934
|
+
);
|
|
8935
|
+
return unwrapApiResponse(result);
|
|
8936
|
+
}
|
|
8937
|
+
/**
|
|
8938
|
+
* Get a specific clip/segment from indexed content.
|
|
8939
|
+
*/
|
|
8940
|
+
async mediaGetClip(params) {
|
|
8941
|
+
const withDefaults = this.withDefaults(params);
|
|
8942
|
+
if (!withDefaults.workspace_id) {
|
|
8943
|
+
throw new Error("workspace_id is required for getting clips");
|
|
8944
|
+
}
|
|
8945
|
+
uuidSchema.parse(params.content_id);
|
|
8946
|
+
const query = new URLSearchParams();
|
|
8947
|
+
if (params.start_time !== void 0) query.set("start_time", String(params.start_time));
|
|
8948
|
+
if (params.end_time !== void 0) query.set("end_time", String(params.end_time));
|
|
8949
|
+
if (params.format) query.set("format", params.format);
|
|
8950
|
+
const suffix = query.toString() ? `?${query.toString()}` : "";
|
|
8951
|
+
const result = await request(
|
|
8952
|
+
this.config,
|
|
8953
|
+
`/workspaces/${withDefaults.workspace_id}/content/${params.content_id}/clip${suffix}`,
|
|
8954
|
+
{ method: "GET" }
|
|
8955
|
+
);
|
|
8956
|
+
return unwrapApiResponse(result);
|
|
8957
|
+
}
|
|
8958
|
+
/**
|
|
8959
|
+
* Delete a content item.
|
|
8960
|
+
*/
|
|
8961
|
+
async mediaDeleteContent(params) {
|
|
8962
|
+
const withDefaults = this.withDefaults(params);
|
|
8963
|
+
if (!withDefaults.workspace_id) {
|
|
8964
|
+
throw new Error("workspace_id is required for deleting content");
|
|
8965
|
+
}
|
|
8966
|
+
uuidSchema.parse(params.content_id);
|
|
8967
|
+
const result = await request(
|
|
8968
|
+
this.config,
|
|
8969
|
+
`/workspaces/${withDefaults.workspace_id}/content/${params.content_id}`,
|
|
8970
|
+
{ method: "DELETE" }
|
|
8971
|
+
);
|
|
8972
|
+
return unwrapApiResponse(result);
|
|
8973
|
+
}
|
|
8834
8974
|
};
|
|
8835
8975
|
|
|
8836
8976
|
// src/tools.ts
|
|
@@ -8872,6 +9012,38 @@ function applyMcpToolPrefix(markdown, toolPrefix) {
|
|
|
8872
9012
|
const toolRegex = new RegExp(`(?<!__)\\b(${toolPattern})\\b(?=\\s*\\()`, "g");
|
|
8873
9013
|
return markdown.replace(toolRegex, `${toolPrefix}$1`);
|
|
8874
9014
|
}
|
|
9015
|
+
var CONTEXTSTREAM_RULES_DYNAMIC = `
|
|
9016
|
+
## ContextStream Dynamic Rules (Powered by our SmartRouter)
|
|
9017
|
+
|
|
9018
|
+
All rules are served dynamically via \`context_smart\`. This file is minimal by design.
|
|
9019
|
+
|
|
9020
|
+
### Every Message Protocol
|
|
9021
|
+
|
|
9022
|
+
| Message | What to Call |
|
|
9023
|
+
|---------|--------------|
|
|
9024
|
+
| **1st message** | \`session_init(folder_path="<cwd>", context_hint="<msg>")\` then \`context_smart(user_message="<msg>")\` |
|
|
9025
|
+
| **2nd+ messages** | \`context_smart(user_message="<msg>")\` |
|
|
9026
|
+
|
|
9027
|
+
### Follow the Instructions Field
|
|
9028
|
+
|
|
9029
|
+
The \`context_smart\` response includes an \`instructions\` field with context-aware guidance.
|
|
9030
|
+
**Follow these instructions.** They are dynamically matched to your query and include:
|
|
9031
|
+
- Search guidance (when/how to search)
|
|
9032
|
+
- Git workflow rules (commit, PR, safety)
|
|
9033
|
+
- Planning rules (use ContextStream plans, not file-based)
|
|
9034
|
+
- Media/code analysis guidance
|
|
9035
|
+
- Lessons from past mistakes
|
|
9036
|
+
- And more...
|
|
9037
|
+
|
|
9038
|
+
### Notices
|
|
9039
|
+
|
|
9040
|
+
Handle notices from \`context_smart\` response:
|
|
9041
|
+
- **[VERSION_NOTICE]**: Tell user to update MCP
|
|
9042
|
+
- **[RULES_NOTICE]**: Run \`generate_rules()\`
|
|
9043
|
+
- **[LESSONS_WARNING]**: Apply lessons immediately
|
|
9044
|
+
|
|
9045
|
+
Rules Version: ${RULES_VERSION}
|
|
9046
|
+
`.trim();
|
|
8875
9047
|
var CONTEXTSTREAM_RULES_FULL = `
|
|
8876
9048
|
## ContextStream Rules
|
|
8877
9049
|
|
|
@@ -9528,8 +9700,8 @@ function getTemplate(editor) {
|
|
|
9528
9700
|
function generateRuleContent(editor, options) {
|
|
9529
9701
|
const template = getTemplate(editor);
|
|
9530
9702
|
if (!template) return null;
|
|
9531
|
-
const mode = options?.mode || "
|
|
9532
|
-
const rules = mode === "full" ? CONTEXTSTREAM_RULES_FULL : CONTEXTSTREAM_RULES_MINIMAL;
|
|
9703
|
+
const mode = options?.mode || "dynamic";
|
|
9704
|
+
const rules = mode === "full" ? CONTEXTSTREAM_RULES_FULL : mode === "minimal" ? CONTEXTSTREAM_RULES_MINIMAL : CONTEXTSTREAM_RULES_DYNAMIC;
|
|
9533
9705
|
let content = template.build(rules);
|
|
9534
9706
|
if (options?.workspaceName || options?.projectName) {
|
|
9535
9707
|
const header = `
|
|
@@ -9636,6 +9808,17 @@ var TOOL_CATALOG = [
|
|
|
9636
9808
|
{ name: "contradictions", hint: "conflicts" }
|
|
9637
9809
|
]
|
|
9638
9810
|
},
|
|
9811
|
+
{
|
|
9812
|
+
name: "Media",
|
|
9813
|
+
tools: [
|
|
9814
|
+
{ name: "index", hint: "add-media" },
|
|
9815
|
+
{ name: "status", hint: "progress" },
|
|
9816
|
+
{ name: "search", hint: "find-clip" },
|
|
9817
|
+
{ name: "get_clip", hint: "get-segment" },
|
|
9818
|
+
{ name: "list", hint: "browse" },
|
|
9819
|
+
{ name: "delete", hint: "remove" }
|
|
9820
|
+
]
|
|
9821
|
+
},
|
|
9639
9822
|
{
|
|
9640
9823
|
name: "Workspace",
|
|
9641
9824
|
tools: [
|
|
@@ -9900,6 +10083,78 @@ def main():
|
|
|
9900
10083
|
print(json.dumps({"hookSpecificOutput": {"hookEventName": "UserPromptSubmit", "additionalContext": REMINDER}}))
|
|
9901
10084
|
sys.exit(0)
|
|
9902
10085
|
|
|
10086
|
+
if __name__ == "__main__":
|
|
10087
|
+
main()
|
|
10088
|
+
`;
|
|
10089
|
+
var MEDIA_AWARE_HOOK_SCRIPT = `#!/usr/bin/env python3
|
|
10090
|
+
"""
|
|
10091
|
+
ContextStream Media-Aware Hook for Claude Code
|
|
10092
|
+
|
|
10093
|
+
Detects media-related prompts and injects context about the media tool.
|
|
10094
|
+
"""
|
|
10095
|
+
|
|
10096
|
+
import json
|
|
10097
|
+
import sys
|
|
10098
|
+
import os
|
|
10099
|
+
import re
|
|
10100
|
+
|
|
10101
|
+
ENABLED = os.environ.get("CONTEXTSTREAM_MEDIA_HOOK_ENABLED", "true").lower() == "true"
|
|
10102
|
+
|
|
10103
|
+
# Media patterns (case-insensitive)
|
|
10104
|
+
PATTERNS = [
|
|
10105
|
+
r"\\b(video|videos|clip|clips|footage|keyframe)s?\\b",
|
|
10106
|
+
r"\\b(remotion|timeline|video\\s*edit)\\b",
|
|
10107
|
+
r"\\b(image|images|photo|photos|picture|thumbnail)s?\\b",
|
|
10108
|
+
r"\\b(audio|podcast|transcript|transcription|voice)\\b",
|
|
10109
|
+
r"\\b(media|asset|assets|creative|b-roll)\\b",
|
|
10110
|
+
r"\\b(find|search|show).*(clip|video|image|audio|footage|media)\\b",
|
|
10111
|
+
]
|
|
10112
|
+
|
|
10113
|
+
COMPILED = [re.compile(p, re.IGNORECASE) for p in PATTERNS]
|
|
10114
|
+
|
|
10115
|
+
MEDIA_CONTEXT = """[MEDIA TOOLS AVAILABLE]
|
|
10116
|
+
Your workspace may have indexed media. Use ContextStream media tools:
|
|
10117
|
+
|
|
10118
|
+
- **Search**: \`mcp__contextstream__media(action="search", query="description")\`
|
|
10119
|
+
- **Get clip**: \`mcp__contextstream__media(action="get_clip", content_id="...", start="1:34", end="2:15", output_format="remotion|ffmpeg|raw")\`
|
|
10120
|
+
- **List assets**: \`mcp__contextstream__media(action="list")\`
|
|
10121
|
+
- **Index**: \`mcp__contextstream__media(action="index", file_path="...", content_type="video|audio|image|document")\`
|
|
10122
|
+
|
|
10123
|
+
For Remotion: use \`output_format="remotion"\` to get frame-based props.
|
|
10124
|
+
[END MEDIA TOOLS]"""
|
|
10125
|
+
|
|
10126
|
+
def matches(text):
|
|
10127
|
+
return any(p.search(text) for p in COMPILED)
|
|
10128
|
+
|
|
10129
|
+
def main():
|
|
10130
|
+
if not ENABLED:
|
|
10131
|
+
sys.exit(0)
|
|
10132
|
+
|
|
10133
|
+
try:
|
|
10134
|
+
data = json.load(sys.stdin)
|
|
10135
|
+
except:
|
|
10136
|
+
sys.exit(0)
|
|
10137
|
+
|
|
10138
|
+
prompt = data.get("prompt", "")
|
|
10139
|
+
if not prompt:
|
|
10140
|
+
session = data.get("session", {})
|
|
10141
|
+
for msg in reversed(session.get("messages", [])):
|
|
10142
|
+
if msg.get("role") == "user":
|
|
10143
|
+
content = msg.get("content", "")
|
|
10144
|
+
prompt = content if isinstance(content, str) else ""
|
|
10145
|
+
if isinstance(content, list):
|
|
10146
|
+
for b in content:
|
|
10147
|
+
if isinstance(b, dict) and b.get("type") == "text":
|
|
10148
|
+
prompt = b.get("text", "")
|
|
10149
|
+
break
|
|
10150
|
+
break
|
|
10151
|
+
|
|
10152
|
+
if not prompt or not matches(prompt):
|
|
10153
|
+
sys.exit(0)
|
|
10154
|
+
|
|
10155
|
+
print(json.dumps({"hookSpecificOutput": {"hookEventName": "UserPromptSubmit", "additionalContext": MEDIA_CONTEXT}}))
|
|
10156
|
+
sys.exit(0)
|
|
10157
|
+
|
|
9903
10158
|
if __name__ == "__main__":
|
|
9904
10159
|
main()
|
|
9905
10160
|
`;
|
|
@@ -10148,6 +10403,31 @@ function buildHooksConfig(options) {
|
|
|
10148
10403
|
const preToolUsePath = path5.join(hooksDir, "contextstream-redirect.py");
|
|
10149
10404
|
const userPromptPath = path5.join(hooksDir, "contextstream-reminder.py");
|
|
10150
10405
|
const preCompactPath = path5.join(hooksDir, "contextstream-precompact.py");
|
|
10406
|
+
const mediaAwarePath = path5.join(hooksDir, "contextstream-media-aware.py");
|
|
10407
|
+
const userPromptHooks = [
|
|
10408
|
+
{
|
|
10409
|
+
matcher: "*",
|
|
10410
|
+
hooks: [
|
|
10411
|
+
{
|
|
10412
|
+
type: "command",
|
|
10413
|
+
command: `python3 "${userPromptPath}"`,
|
|
10414
|
+
timeout: 5
|
|
10415
|
+
}
|
|
10416
|
+
]
|
|
10417
|
+
}
|
|
10418
|
+
];
|
|
10419
|
+
if (options?.includeMediaAware !== false) {
|
|
10420
|
+
userPromptHooks.push({
|
|
10421
|
+
matcher: "*",
|
|
10422
|
+
hooks: [
|
|
10423
|
+
{
|
|
10424
|
+
type: "command",
|
|
10425
|
+
command: `python3 "${mediaAwarePath}"`,
|
|
10426
|
+
timeout: 5
|
|
10427
|
+
}
|
|
10428
|
+
]
|
|
10429
|
+
});
|
|
10430
|
+
}
|
|
10151
10431
|
const config = {
|
|
10152
10432
|
PreToolUse: [
|
|
10153
10433
|
{
|
|
@@ -10161,18 +10441,7 @@ function buildHooksConfig(options) {
|
|
|
10161
10441
|
]
|
|
10162
10442
|
}
|
|
10163
10443
|
],
|
|
10164
|
-
UserPromptSubmit:
|
|
10165
|
-
{
|
|
10166
|
-
matcher: "*",
|
|
10167
|
-
hooks: [
|
|
10168
|
-
{
|
|
10169
|
-
type: "command",
|
|
10170
|
-
command: `python3 "${userPromptPath}"`,
|
|
10171
|
-
timeout: 5
|
|
10172
|
-
}
|
|
10173
|
-
]
|
|
10174
|
-
}
|
|
10175
|
-
]
|
|
10444
|
+
UserPromptSubmit: userPromptHooks
|
|
10176
10445
|
};
|
|
10177
10446
|
if (options?.includePreCompact) {
|
|
10178
10447
|
config.PreCompact = [
|
|
@@ -10197,6 +10466,7 @@ async function installHookScripts(options) {
|
|
|
10197
10466
|
const preToolUsePath = path5.join(hooksDir, "contextstream-redirect.py");
|
|
10198
10467
|
const userPromptPath = path5.join(hooksDir, "contextstream-reminder.py");
|
|
10199
10468
|
const preCompactPath = path5.join(hooksDir, "contextstream-precompact.py");
|
|
10469
|
+
const mediaAwarePath = path5.join(hooksDir, "contextstream-media-aware.py");
|
|
10200
10470
|
await fs4.writeFile(preToolUsePath, PRETOOLUSE_HOOK_SCRIPT, { mode: 493 });
|
|
10201
10471
|
await fs4.writeFile(userPromptPath, USER_PROMPT_HOOK_SCRIPT, { mode: 493 });
|
|
10202
10472
|
const result = {
|
|
@@ -10207,6 +10477,10 @@ async function installHookScripts(options) {
|
|
|
10207
10477
|
await fs4.writeFile(preCompactPath, PRECOMPACT_HOOK_SCRIPT, { mode: 493 });
|
|
10208
10478
|
result.preCompact = preCompactPath;
|
|
10209
10479
|
}
|
|
10480
|
+
if (options?.includeMediaAware !== false) {
|
|
10481
|
+
await fs4.writeFile(mediaAwarePath, MEDIA_AWARE_HOOK_SCRIPT, { mode: 493 });
|
|
10482
|
+
result.mediaAware = mediaAwarePath;
|
|
10483
|
+
}
|
|
10210
10484
|
return result;
|
|
10211
10485
|
}
|
|
10212
10486
|
async function readClaudeSettings(scope, projectPath) {
|
|
@@ -10882,6 +11156,7 @@ var TOOL_DISPLAY_NAMES = {
|
|
|
10882
11156
|
search: "search",
|
|
10883
11157
|
memory: "memory",
|
|
10884
11158
|
graph: "graph",
|
|
11159
|
+
media: "media",
|
|
10885
11160
|
ai: "ai",
|
|
10886
11161
|
generate_rules: "rules",
|
|
10887
11162
|
generate_editor_rules: "rules",
|
|
@@ -12265,6 +12540,8 @@ var CONSOLIDATED_TOOLS = /* @__PURE__ */ new Set([
|
|
|
12265
12540
|
// Consolidates reminders_list, reminders_create, etc.
|
|
12266
12541
|
"integration",
|
|
12267
12542
|
// Consolidates slack_*, github_*, notion_*, integrations_*
|
|
12543
|
+
"media",
|
|
12544
|
+
// Consolidates media indexing, search, and clip retrieval for Remotion/FFmpeg
|
|
12268
12545
|
"help"
|
|
12269
12546
|
// Consolidates session_tools, auth_me, mcp_server_version, etc.
|
|
12270
12547
|
]);
|
|
@@ -19663,6 +19940,430 @@ Last edited: ${updatedPage.last_edited_time}`
|
|
|
19663
19940
|
}
|
|
19664
19941
|
}
|
|
19665
19942
|
);
|
|
19943
|
+
registerTool(
|
|
19944
|
+
"media",
|
|
19945
|
+
{
|
|
19946
|
+
title: "Media",
|
|
19947
|
+
description: `Media operations for video/audio/image assets. Enables AI agents to index, search, and retrieve media with semantic understanding - solving the "LLM as video editor has no context" problem for tools like Remotion.
|
|
19948
|
+
|
|
19949
|
+
Actions:
|
|
19950
|
+
- index: Index a local media file or external URL. Triggers ML processing (Whisper transcription, CLIP embeddings, keyframe extraction).
|
|
19951
|
+
- status: Check indexing progress for a content_id. Returns transcript_available, keyframe_count, duration.
|
|
19952
|
+
- search: Semantic search across indexed media. Returns timestamps, transcript excerpts, keyframe URLs.
|
|
19953
|
+
- get_clip: Get clip details for a time range. Supports output_format: remotion (frame-based props), ffmpeg (timecodes), raw.
|
|
19954
|
+
- list: List indexed media assets.
|
|
19955
|
+
- delete: Remove a media asset from the index.
|
|
19956
|
+
|
|
19957
|
+
Example workflow:
|
|
19958
|
+
1. media(action="index", file_path="/path/to/video.mp4") \u2192 get content_id
|
|
19959
|
+
2. media(action="status", content_id="...") \u2192 wait for indexed
|
|
19960
|
+
3. media(action="search", query="where John explains authentication") \u2192 get timestamps
|
|
19961
|
+
4. media(action="get_clip", content_id="...", start="1:34", end="2:15", output_format="remotion") \u2192 get Remotion props`,
|
|
19962
|
+
inputSchema: external_exports.object({
|
|
19963
|
+
action: external_exports.enum(["index", "status", "search", "get_clip", "list", "delete"]).describe("Action to perform"),
|
|
19964
|
+
workspace_id: external_exports.string().uuid().optional(),
|
|
19965
|
+
project_id: external_exports.string().uuid().optional(),
|
|
19966
|
+
// Index params
|
|
19967
|
+
file_path: external_exports.string().optional().describe("Local path to media file for indexing"),
|
|
19968
|
+
external_url: external_exports.string().url().optional().describe("External URL to media file for indexing"),
|
|
19969
|
+
content_type: external_exports.enum(["video", "audio", "image", "document"]).optional().describe("Type of media content (auto-detected if not provided)"),
|
|
19970
|
+
// Status/get_clip/delete params
|
|
19971
|
+
content_id: external_exports.string().uuid().optional().describe("Content ID from index operation"),
|
|
19972
|
+
// Search params
|
|
19973
|
+
query: external_exports.string().optional().describe("Semantic search query for media content"),
|
|
19974
|
+
content_types: external_exports.array(external_exports.enum(["video", "audio", "image", "document"])).optional().describe("Filter search to specific content types"),
|
|
19975
|
+
// Get clip params
|
|
19976
|
+
start: external_exports.string().optional().describe('Start time for clip. Formats: "1:34", "94s", or seconds as string'),
|
|
19977
|
+
end: external_exports.string().optional().describe('End time for clip. Formats: "2:15", "135s", or seconds as string'),
|
|
19978
|
+
output_format: external_exports.enum(["remotion", "ffmpeg", "raw"]).optional().describe(
|
|
19979
|
+
"Output format: remotion (frame-based props for Video component), ffmpeg (timecodes), raw (seconds)"
|
|
19980
|
+
),
|
|
19981
|
+
fps: external_exports.number().optional().describe("Frames per second for remotion format (default: 30)"),
|
|
19982
|
+
// Common params
|
|
19983
|
+
tags: external_exports.array(external_exports.string()).optional().describe("Tags to associate with media"),
|
|
19984
|
+
limit: external_exports.number().optional().describe("Maximum results to return")
|
|
19985
|
+
})
|
|
19986
|
+
},
|
|
19987
|
+
async (input) => {
|
|
19988
|
+
const workspaceId = resolveWorkspaceId(input.workspace_id);
|
|
19989
|
+
const projectId = resolveProjectId(input.project_id);
|
|
19990
|
+
switch (input.action) {
|
|
19991
|
+
case "index": {
|
|
19992
|
+
if (!input.file_path && !input.external_url) {
|
|
19993
|
+
return errorResult("index requires: file_path or external_url");
|
|
19994
|
+
}
|
|
19995
|
+
if (!workspaceId) {
|
|
19996
|
+
return errorResult(
|
|
19997
|
+
"Error: workspace_id is required. Please call session_init first or provide workspace_id explicitly."
|
|
19998
|
+
);
|
|
19999
|
+
}
|
|
20000
|
+
if (input.file_path) {
|
|
20001
|
+
const fs8 = await import("fs/promises");
|
|
20002
|
+
const pathModule = await import("path");
|
|
20003
|
+
const filePath = input.file_path.startsWith("~") ? input.file_path.replace("~", process.env.HOME || "") : input.file_path;
|
|
20004
|
+
const resolvedPath = pathModule.resolve(filePath);
|
|
20005
|
+
let fileStats;
|
|
20006
|
+
try {
|
|
20007
|
+
fileStats = await fs8.stat(resolvedPath);
|
|
20008
|
+
} catch {
|
|
20009
|
+
return errorResult(`File not found: ${resolvedPath}`);
|
|
20010
|
+
}
|
|
20011
|
+
if (!fileStats.isFile()) {
|
|
20012
|
+
return errorResult(`Not a file: ${resolvedPath}`);
|
|
20013
|
+
}
|
|
20014
|
+
const ext = pathModule.extname(resolvedPath).toLowerCase();
|
|
20015
|
+
const mimeTypes = {
|
|
20016
|
+
".mp4": "video/mp4",
|
|
20017
|
+
".webm": "video/webm",
|
|
20018
|
+
".mov": "video/quicktime",
|
|
20019
|
+
".avi": "video/x-msvideo",
|
|
20020
|
+
".mkv": "video/x-matroska",
|
|
20021
|
+
".mp3": "audio/mpeg",
|
|
20022
|
+
".wav": "audio/wav",
|
|
20023
|
+
".ogg": "audio/ogg",
|
|
20024
|
+
".flac": "audio/flac",
|
|
20025
|
+
".m4a": "audio/mp4",
|
|
20026
|
+
".png": "image/png",
|
|
20027
|
+
".jpg": "image/jpeg",
|
|
20028
|
+
".jpeg": "image/jpeg",
|
|
20029
|
+
".gif": "image/gif",
|
|
20030
|
+
".webp": "image/webp",
|
|
20031
|
+
".svg": "image/svg+xml"
|
|
20032
|
+
};
|
|
20033
|
+
const mimeType = mimeTypes[ext] || "application/octet-stream";
|
|
20034
|
+
let contentType = "other";
|
|
20035
|
+
if (input.content_type) {
|
|
20036
|
+
contentType = input.content_type;
|
|
20037
|
+
} else if (mimeType.startsWith("video/")) {
|
|
20038
|
+
contentType = "video";
|
|
20039
|
+
} else if (mimeType.startsWith("audio/")) {
|
|
20040
|
+
contentType = "audio";
|
|
20041
|
+
} else if (mimeType.startsWith("image/")) {
|
|
20042
|
+
contentType = "image";
|
|
20043
|
+
}
|
|
20044
|
+
const filename = pathModule.basename(resolvedPath);
|
|
20045
|
+
try {
|
|
20046
|
+
const uploadInit = await client.mediaInitUpload({
|
|
20047
|
+
workspace_id: workspaceId,
|
|
20048
|
+
filename,
|
|
20049
|
+
size_bytes: fileStats.size,
|
|
20050
|
+
content_type: contentType,
|
|
20051
|
+
mime_type: mimeType,
|
|
20052
|
+
tags: input.tags
|
|
20053
|
+
});
|
|
20054
|
+
const fileBuffer = await fs8.readFile(resolvedPath);
|
|
20055
|
+
const uploadResponse = await fetch(uploadInit.upload_url, {
|
|
20056
|
+
method: "PUT",
|
|
20057
|
+
headers: uploadInit.headers,
|
|
20058
|
+
body: fileBuffer
|
|
20059
|
+
});
|
|
20060
|
+
if (!uploadResponse.ok) {
|
|
20061
|
+
return errorResult(
|
|
20062
|
+
`Failed to upload file: ${uploadResponse.status} ${uploadResponse.statusText}`
|
|
20063
|
+
);
|
|
20064
|
+
}
|
|
20065
|
+
await client.mediaCompleteUpload({
|
|
20066
|
+
workspace_id: workspaceId,
|
|
20067
|
+
content_id: uploadInit.content_id
|
|
20068
|
+
});
|
|
20069
|
+
const result2 = {
|
|
20070
|
+
status: "uploaded",
|
|
20071
|
+
message: "Media file uploaded successfully. Indexing has been triggered.",
|
|
20072
|
+
content_id: uploadInit.content_id,
|
|
20073
|
+
filename,
|
|
20074
|
+
content_type: contentType,
|
|
20075
|
+
size_bytes: fileStats.size,
|
|
20076
|
+
mime_type: mimeType,
|
|
20077
|
+
note: "Use media(action='status', content_id='...') to check indexing progress."
|
|
20078
|
+
};
|
|
20079
|
+
return {
|
|
20080
|
+
content: [
|
|
20081
|
+
{
|
|
20082
|
+
type: "text",
|
|
20083
|
+
text: `\u2705 Media uploaded successfully!
|
|
20084
|
+
|
|
20085
|
+
Content ID: ${uploadInit.content_id}
|
|
20086
|
+
Filename: ${filename}
|
|
20087
|
+
Type: ${contentType}
|
|
20088
|
+
Size: ${(fileStats.size / 1024 / 1024).toFixed(2)} MB
|
|
20089
|
+
|
|
20090
|
+
Indexing has been triggered. Use media(action='status', content_id='${uploadInit.content_id}') to check progress.`
|
|
20091
|
+
}
|
|
20092
|
+
],
|
|
20093
|
+
structuredContent: toStructured(result2)
|
|
20094
|
+
};
|
|
20095
|
+
} catch (err) {
|
|
20096
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
20097
|
+
return errorResult(`Failed to upload media: ${errMsg}`);
|
|
20098
|
+
}
|
|
20099
|
+
}
|
|
20100
|
+
const result = {
|
|
20101
|
+
status: "not_implemented",
|
|
20102
|
+
message: "External URL indexing is not yet implemented. Please use file_path for local files instead.",
|
|
20103
|
+
action: "index",
|
|
20104
|
+
external_url: input.external_url,
|
|
20105
|
+
content_type: input.content_type
|
|
20106
|
+
};
|
|
20107
|
+
return {
|
|
20108
|
+
content: [{ type: "text", text: formatContent(result) }],
|
|
20109
|
+
structuredContent: toStructured(result)
|
|
20110
|
+
};
|
|
20111
|
+
}
|
|
20112
|
+
case "status": {
|
|
20113
|
+
if (!input.content_id) {
|
|
20114
|
+
return errorResult("status requires: content_id");
|
|
20115
|
+
}
|
|
20116
|
+
if (!workspaceId) {
|
|
20117
|
+
return errorResult(
|
|
20118
|
+
"Error: workspace_id is required. Please call session_init first or provide workspace_id explicitly."
|
|
20119
|
+
);
|
|
20120
|
+
}
|
|
20121
|
+
try {
|
|
20122
|
+
const content = await client.mediaGetContent({
|
|
20123
|
+
workspace_id: workspaceId,
|
|
20124
|
+
content_id: input.content_id
|
|
20125
|
+
});
|
|
20126
|
+
let statusEmoji = "\u23F3";
|
|
20127
|
+
let statusMessage = "Pending";
|
|
20128
|
+
if (content.status === "indexed" || content.status === "ready") {
|
|
20129
|
+
statusEmoji = "\u2705";
|
|
20130
|
+
statusMessage = "Indexed and ready";
|
|
20131
|
+
} else if (content.status === "processing" || content.status === "indexing") {
|
|
20132
|
+
statusEmoji = "\u{1F504}";
|
|
20133
|
+
statusMessage = `Processing${content.indexing_progress ? ` (${content.indexing_progress}%)` : ""}`;
|
|
20134
|
+
} else if (content.status === "error" || content.status === "failed") {
|
|
20135
|
+
statusEmoji = "\u274C";
|
|
20136
|
+
statusMessage = content.indexing_error || "Indexing failed";
|
|
20137
|
+
}
|
|
20138
|
+
return {
|
|
20139
|
+
content: [
|
|
20140
|
+
{
|
|
20141
|
+
type: "text",
|
|
20142
|
+
text: `${statusEmoji} ${content.filename}
|
|
20143
|
+
|
|
20144
|
+
Status: ${statusMessage}
|
|
20145
|
+
Content ID: ${content.id}
|
|
20146
|
+
Type: ${content.content_type}
|
|
20147
|
+
Size: ${(content.size_bytes / 1024 / 1024).toFixed(2)} MB
|
|
20148
|
+
Created: ${content.created_at}`
|
|
20149
|
+
}
|
|
20150
|
+
],
|
|
20151
|
+
structuredContent: toStructured(content)
|
|
20152
|
+
};
|
|
20153
|
+
} catch (err) {
|
|
20154
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
20155
|
+
return errorResult(`Failed to get content status: ${errMsg}`);
|
|
20156
|
+
}
|
|
20157
|
+
}
|
|
20158
|
+
case "search": {
|
|
20159
|
+
if (!input.query) {
|
|
20160
|
+
return errorResult("search requires: query");
|
|
20161
|
+
}
|
|
20162
|
+
if (!workspaceId) {
|
|
20163
|
+
return errorResult(
|
|
20164
|
+
"Error: workspace_id is required. Please call session_init first or provide workspace_id explicitly."
|
|
20165
|
+
);
|
|
20166
|
+
}
|
|
20167
|
+
try {
|
|
20168
|
+
const searchResult = await client.mediaSearchContent({
|
|
20169
|
+
workspace_id: workspaceId,
|
|
20170
|
+
query: input.query,
|
|
20171
|
+
content_type: input.content_types?.[0],
|
|
20172
|
+
// API accepts single type for now
|
|
20173
|
+
limit: input.limit
|
|
20174
|
+
});
|
|
20175
|
+
if (searchResult.results.length === 0) {
|
|
20176
|
+
return {
|
|
20177
|
+
content: [
|
|
20178
|
+
{
|
|
20179
|
+
type: "text",
|
|
20180
|
+
text: `No results found for: "${input.query}"
|
|
20181
|
+
|
|
20182
|
+
Try a different query or check that you have indexed media content.`
|
|
20183
|
+
}
|
|
20184
|
+
],
|
|
20185
|
+
structuredContent: toStructured(searchResult)
|
|
20186
|
+
};
|
|
20187
|
+
}
|
|
20188
|
+
const resultsText = searchResult.results.map((r, i) => {
|
|
20189
|
+
let timeInfo = "";
|
|
20190
|
+
if (r.timestamp_start !== void 0) {
|
|
20191
|
+
const startMin = Math.floor(r.timestamp_start / 60);
|
|
20192
|
+
const startSec = Math.floor(r.timestamp_start % 60);
|
|
20193
|
+
timeInfo = ` @ ${startMin}:${startSec.toString().padStart(2, "0")}`;
|
|
20194
|
+
if (r.timestamp_end !== void 0) {
|
|
20195
|
+
const endMin = Math.floor(r.timestamp_end / 60);
|
|
20196
|
+
const endSec = Math.floor(r.timestamp_end % 60);
|
|
20197
|
+
timeInfo += `-${endMin}:${endSec.toString().padStart(2, "0")}`;
|
|
20198
|
+
}
|
|
20199
|
+
}
|
|
20200
|
+
const matchText = r.match_text ? `
|
|
20201
|
+
"${r.match_text}"` : "";
|
|
20202
|
+
return `${i + 1}. ${r.filename} (${r.content_type})${timeInfo}${matchText}`;
|
|
20203
|
+
}).join("\n\n");
|
|
20204
|
+
return {
|
|
20205
|
+
content: [
|
|
20206
|
+
{
|
|
20207
|
+
type: "text",
|
|
20208
|
+
text: `Found ${searchResult.total} result(s) for: "${input.query}"
|
|
20209
|
+
|
|
20210
|
+
${resultsText}`
|
|
20211
|
+
}
|
|
20212
|
+
],
|
|
20213
|
+
structuredContent: toStructured(searchResult)
|
|
20214
|
+
};
|
|
20215
|
+
} catch (err) {
|
|
20216
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
20217
|
+
return errorResult(`Failed to search media: ${errMsg}`);
|
|
20218
|
+
}
|
|
20219
|
+
}
|
|
20220
|
+
case "get_clip": {
|
|
20221
|
+
if (!input.content_id) {
|
|
20222
|
+
return errorResult("get_clip requires: content_id");
|
|
20223
|
+
}
|
|
20224
|
+
if (!workspaceId) {
|
|
20225
|
+
return errorResult(
|
|
20226
|
+
"Error: workspace_id is required. Please call session_init first or provide workspace_id explicitly."
|
|
20227
|
+
);
|
|
20228
|
+
}
|
|
20229
|
+
const parseTime = (t) => {
|
|
20230
|
+
if (!t) return void 0;
|
|
20231
|
+
if (t.includes(":")) {
|
|
20232
|
+
const parts = t.split(":").map(Number);
|
|
20233
|
+
if (parts.length === 2) return parts[0] * 60 + parts[1];
|
|
20234
|
+
if (parts.length === 3) return parts[0] * 3600 + parts[1] * 60 + parts[2];
|
|
20235
|
+
}
|
|
20236
|
+
if (t.endsWith("s")) return parseFloat(t.slice(0, -1));
|
|
20237
|
+
return parseFloat(t);
|
|
20238
|
+
};
|
|
20239
|
+
const startTime = parseTime(input.start);
|
|
20240
|
+
const endTime = parseTime(input.end);
|
|
20241
|
+
try {
|
|
20242
|
+
const clipResult = await client.mediaGetClip({
|
|
20243
|
+
workspace_id: workspaceId,
|
|
20244
|
+
content_id: input.content_id,
|
|
20245
|
+
start_time: startTime,
|
|
20246
|
+
end_time: endTime,
|
|
20247
|
+
format: input.output_format === "remotion" ? "remotion" : input.output_format === "ffmpeg" ? "ffmpeg" : "json"
|
|
20248
|
+
});
|
|
20249
|
+
let outputText = `\u{1F4F9} Clip from: ${clipResult.filename}
|
|
20250
|
+
|
|
20251
|
+
`;
|
|
20252
|
+
outputText += `Time range: ${clipResult.clip.start_time}s - ${clipResult.clip.end_time}s
|
|
20253
|
+
`;
|
|
20254
|
+
if (clipResult.clip.transcript) {
|
|
20255
|
+
outputText += `
|
|
20256
|
+
Transcript:
|
|
20257
|
+
"${clipResult.clip.transcript}"
|
|
20258
|
+
`;
|
|
20259
|
+
}
|
|
20260
|
+
if (clipResult.remotion_props && input.output_format === "remotion") {
|
|
20261
|
+
outputText += `
|
|
20262
|
+
### Remotion Props:
|
|
20263
|
+
\`\`\`json
|
|
20264
|
+
${JSON.stringify(clipResult.remotion_props, null, 2)}
|
|
20265
|
+
\`\`\``;
|
|
20266
|
+
}
|
|
20267
|
+
if (clipResult.ffmpeg_command && input.output_format === "ffmpeg") {
|
|
20268
|
+
outputText += `
|
|
20269
|
+
### FFmpeg Command:
|
|
20270
|
+
\`\`\`bash
|
|
20271
|
+
${clipResult.ffmpeg_command}
|
|
20272
|
+
\`\`\``;
|
|
20273
|
+
}
|
|
20274
|
+
return {
|
|
20275
|
+
content: [{ type: "text", text: outputText }],
|
|
20276
|
+
structuredContent: toStructured(clipResult)
|
|
20277
|
+
};
|
|
20278
|
+
} catch (err) {
|
|
20279
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
20280
|
+
return errorResult(`Failed to get clip: ${errMsg}`);
|
|
20281
|
+
}
|
|
20282
|
+
}
|
|
20283
|
+
case "list": {
|
|
20284
|
+
if (!workspaceId) {
|
|
20285
|
+
return errorResult(
|
|
20286
|
+
"Error: workspace_id is required. Please call session_init first or provide workspace_id explicitly."
|
|
20287
|
+
);
|
|
20288
|
+
}
|
|
20289
|
+
try {
|
|
20290
|
+
const listResult = await client.mediaListContent({
|
|
20291
|
+
workspace_id: workspaceId,
|
|
20292
|
+
content_type: input.content_types?.[0],
|
|
20293
|
+
// API accepts single type for now
|
|
20294
|
+
limit: input.limit
|
|
20295
|
+
});
|
|
20296
|
+
if (listResult.items.length === 0) {
|
|
20297
|
+
return {
|
|
20298
|
+
content: [
|
|
20299
|
+
{
|
|
20300
|
+
type: "text",
|
|
20301
|
+
text: "No media content found.\n\nUse media(action='index', file_path='...') to index media files."
|
|
20302
|
+
}
|
|
20303
|
+
],
|
|
20304
|
+
structuredContent: toStructured(listResult)
|
|
20305
|
+
};
|
|
20306
|
+
}
|
|
20307
|
+
const itemsText = listResult.items.map((item, i) => {
|
|
20308
|
+
const statusEmoji = item.status === "indexed" || item.status === "ready" ? "\u2705" : item.status === "processing" ? "\u{1F504}" : item.status === "error" ? "\u274C" : "\u23F3";
|
|
20309
|
+
const size = (item.size_bytes / 1024 / 1024).toFixed(2);
|
|
20310
|
+
return `${i + 1}. ${statusEmoji} ${item.filename}
|
|
20311
|
+
Type: ${item.content_type} | Size: ${size} MB | Status: ${item.status}
|
|
20312
|
+
ID: ${item.id}`;
|
|
20313
|
+
}).join("\n\n");
|
|
20314
|
+
return {
|
|
20315
|
+
content: [
|
|
20316
|
+
{
|
|
20317
|
+
type: "text",
|
|
20318
|
+
text: `\u{1F4DA} Media Library (${listResult.total} items)
|
|
20319
|
+
|
|
20320
|
+
${itemsText}`
|
|
20321
|
+
}
|
|
20322
|
+
],
|
|
20323
|
+
structuredContent: toStructured(listResult)
|
|
20324
|
+
};
|
|
20325
|
+
} catch (err) {
|
|
20326
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
20327
|
+
return errorResult(`Failed to list media: ${errMsg}`);
|
|
20328
|
+
}
|
|
20329
|
+
}
|
|
20330
|
+
case "delete": {
|
|
20331
|
+
if (!input.content_id) {
|
|
20332
|
+
return errorResult("delete requires: content_id");
|
|
20333
|
+
}
|
|
20334
|
+
if (!workspaceId) {
|
|
20335
|
+
return errorResult(
|
|
20336
|
+
"Error: workspace_id is required. Please call session_init first or provide workspace_id explicitly."
|
|
20337
|
+
);
|
|
20338
|
+
}
|
|
20339
|
+
try {
|
|
20340
|
+
const deleteResult = await client.mediaDeleteContent({
|
|
20341
|
+
workspace_id: workspaceId,
|
|
20342
|
+
content_id: input.content_id
|
|
20343
|
+
});
|
|
20344
|
+
return {
|
|
20345
|
+
content: [
|
|
20346
|
+
{
|
|
20347
|
+
type: "text",
|
|
20348
|
+
text: deleteResult.deleted ? `\u2705 Content deleted successfully.
|
|
20349
|
+
|
|
20350
|
+
Content ID: ${input.content_id}` : `\u26A0\uFE0F Content may not have been deleted.
|
|
20351
|
+
|
|
20352
|
+
Content ID: ${input.content_id}`
|
|
20353
|
+
}
|
|
20354
|
+
],
|
|
20355
|
+
structuredContent: toStructured(deleteResult)
|
|
20356
|
+
};
|
|
20357
|
+
} catch (err) {
|
|
20358
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
20359
|
+
return errorResult(`Failed to delete content: ${errMsg}`);
|
|
20360
|
+
}
|
|
20361
|
+
}
|
|
20362
|
+
default:
|
|
20363
|
+
return errorResult(`Unknown action: ${input.action}`);
|
|
20364
|
+
}
|
|
20365
|
+
}
|
|
20366
|
+
);
|
|
19666
20367
|
registerTool(
|
|
19667
20368
|
"help",
|
|
19668
20369
|
{
|
|
@@ -22193,7 +22894,8 @@ function buildClientConfig(params) {
|
|
|
22193
22894
|
defaultWorkspaceId: void 0,
|
|
22194
22895
|
defaultProjectId: void 0,
|
|
22195
22896
|
userAgent: `contextstream-mcp/setup/${VERSION}`,
|
|
22196
|
-
contextPackEnabled: true
|
|
22897
|
+
contextPackEnabled: true,
|
|
22898
|
+
showTiming: false
|
|
22197
22899
|
};
|
|
22198
22900
|
}
|
|
22199
22901
|
async function runSetupWizard(args) {
|
|
@@ -22440,7 +23142,15 @@ Code: ${device.user_code}`);
|
|
|
22440
23142
|
}
|
|
22441
23143
|
}
|
|
22442
23144
|
}
|
|
22443
|
-
|
|
23145
|
+
console.log("\nRules mode (how rules are delivered to the AI):");
|
|
23146
|
+
console.log(" 1) Dynamic (recommended) \u2014 minimal rules file, context_smart delivers rules dynamically");
|
|
23147
|
+
console.log(" Best for: efficiency, better results, rules always up-to-date");
|
|
23148
|
+
console.log(" The rules file just tells the AI to call session_init + context_smart");
|
|
23149
|
+
console.log(" 2) Full \u2014 complete rules embedded in file");
|
|
23150
|
+
console.log(" Best for: offline use, debugging, or if you prefer static rules");
|
|
23151
|
+
console.log("");
|
|
23152
|
+
const ruleModeChoice = normalizeInput(await rl.question("Choose [1/2] (default 1): ")) || "1";
|
|
23153
|
+
const mode = ruleModeChoice === "2" ? "full" : "dynamic";
|
|
22444
23154
|
const detectedPlanName = await client.getPlanName();
|
|
22445
23155
|
const detectedGraphTier = await client.getGraphTier();
|
|
22446
23156
|
const graphTierLabel = detectedGraphTier === "full" ? "full graph" : detectedGraphTier === "lite" ? "graph-lite" : "none";
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@contextstream/mcp-server",
|
|
3
3
|
"mcpName": "io.github.contextstreamio/mcp-server",
|
|
4
|
-
"version": "0.4.
|
|
4
|
+
"version": "0.4.44",
|
|
5
5
|
"description": "ContextStream MCP server - v0.4.x with consolidated domain tools (~11 tools, ~75% token reduction). Code context, memory, search, and AI tools.",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"license": "MIT",
|