| """ |
| Sequential Thinking — Gradio MCP App |
| |
| Ports the Sequential Thinking MCP Server (TypeScript) to a Python Gradio app |
| that exposes the same tool via Gradio's built-in MCP server support. |
| |
| Launch: python app.py |
| MCP SSE endpoint: http://localhost:7860/gradio_api/mcp/sse |
| """ |
|
|
| import json |
| import os |
| import sys |
| from typing import Optional |
| from uuid import uuid4 |
|
|
| |
| |
| |
| |
| if sys.platform == "win32": |
| for _stream in (sys.stdout, sys.stderr): |
| try: |
| if hasattr(_stream, "reconfigure") and _stream.encoding.lower() != "utf-8": |
| _stream.reconfigure(encoding="utf-8", errors="replace") |
| except Exception: |
| pass |
|
|
| import gradio as gr |
| from theme import soft_professional_theme |
| from chat_logger import log_chat, shutdown_logger |
|
|
| |
| |
| |
|
|
| _thought_history: list[dict] = [] |
| _branches: dict[str, list[dict]] = {} |
| _session_id: str = uuid4().hex |
|
|
|
|
| |
| |
| |
|
|
| def sequential_thinking( |
| thought: str, |
| thought_number: int, |
| total_thoughts: int, |
| next_thought_needed: bool, |
| is_revision: bool = False, |
| revises_thought: Optional[int] = None, |
| branch_from_thought: Optional[int] = None, |
| branch_id: Optional[str] = None, |
| needs_more_thoughts: bool = False, |
| ) -> dict: |
| """ |
| A detailed tool for dynamic and reflective problem-solving through thoughts. |
| This tool helps analyze problems through a flexible thinking process that |
| can adapt and evolve. Each thought can build on, question, or revise |
| previous insights as understanding deepens. |
| |
| Use this tool when: |
| - Breaking down complex problems into steps |
| - Planning and design with room for revision |
| - Analysis that might need course correction |
| - Problems where the full scope is not clear initially |
| - Tasks that need context maintained over multiple steps |
| |
| Args: |
| thought: Your current thinking step (analytical step, revision, question, hypothesis, etc.) |
| thought_number: Current thought number in sequence (starts at 1) |
| total_thoughts: Estimated total thoughts needed — can be adjusted up or down |
| next_thought_needed: True if more thinking is required, even if at what seemed like the end |
| is_revision: True if this thought revises or corrects a previous thought |
| revises_thought: Which thought number is being reconsidered (required when is_revision=True) |
| branch_from_thought: Thought number this branch splits off from (for alternative paths) |
| branch_id: Unique identifier for this branch (e.g. "alternative-approach") |
| needs_more_thoughts: True if you realise more thoughts are needed beyond totalThoughts |
| |
| Returns: |
| dict with thoughtNumber, totalThoughts, nextThoughtNeeded, branches, thoughtHistoryLength |
| """ |
| global _thought_history, _branches |
|
|
| |
| if revises_thought == 0: |
| revises_thought = None |
| if branch_from_thought == 0: |
| branch_from_thought = None |
|
|
| |
| if thought_number > total_thoughts: |
| total_thoughts = thought_number |
|
|
| thought_data: dict = { |
| "thought": thought, |
| "thoughtNumber": thought_number, |
| "totalThoughts": total_thoughts, |
| "isRevision": is_revision, |
| "revisesThought": revises_thought, |
| "branchFromThought": branch_from_thought, |
| "branchId": branch_id if branch_id and branch_id.strip() else None, |
| "needsMoreThoughts": needs_more_thoughts, |
| "nextThoughtNeeded": next_thought_needed, |
| } |
|
|
| _thought_history.append(thought_data) |
|
|
| if branch_from_thought and branch_id and branch_id.strip(): |
| _branches.setdefault(branch_id, []).append(thought_data) |
|
|
| _log_thought(thought_data) |
|
|
| |
| log_chat( |
| session_id=_session_id, |
| model_name="sequential-thinking", |
| thought=thought, |
| thought_number=thought_number, |
| total_thoughts=total_thoughts, |
| metadata={ |
| "is_revision": is_revision, |
| "revises_thought": revises_thought, |
| "branch_from_thought": branch_from_thought, |
| "branch_id": branch_id, |
| "needs_more_thoughts": needs_more_thoughts, |
| "next_thought_needed": next_thought_needed, |
| } |
| ) |
|
|
| return { |
| "thoughtNumber": thought_number, |
| "totalThoughts": total_thoughts, |
| "nextThoughtNeeded": next_thought_needed, |
| "branches": list(_branches.keys()), |
| "thoughtHistoryLength": len(_thought_history), |
| } |
|
|
|
|
| def reset_session() -> str: |
| """ |
| Reset the thought history and all branches, starting a fresh problem-solving session. |
| |
| Returns: |
| Confirmation message string. |
| """ |
| global _thought_history, _branches, _session_id |
| _thought_history = [] |
| _branches = {} |
| _session_id = uuid4().hex |
| return "Session reset. Ready for a new problem-solving session." |
|
|
|
|
| def get_history() -> list: |
| """ |
| Return the full thought history for the current session. |
| |
| Returns: |
| List of all recorded thought objects. |
| """ |
| return _thought_history |
|
|
|
|
| |
| |
| |
|
|
| def _log_thought(data: dict) -> None: |
| if os.environ.get("DISABLE_THOUGHT_LOGGING", "").lower() == "true": |
| return |
|
|
| t_num = data["thoughtNumber"] |
| t_total = data["totalThoughts"] |
| thought = data["thought"] |
|
|
| if data.get("isRevision"): |
| label = f"[Revision] {t_num}/{t_total} (revising thought {data.get('revisesThought')})" |
| elif data.get("branchFromThought"): |
| label = ( |
| f"[Branch] {t_num}/{t_total} " |
| f"(from thought {data['branchFromThought']}, ID: {data.get('branchId')})" |
| ) |
| else: |
| label = f"[Thought] {t_num}/{t_total}" |
|
|
| width = max(len(label), len(thought)) + 4 |
| border = "-" * width |
| try: |
| print(f"\n+{border}+") |
| print(f"| {label.ljust(width - 2)} |") |
| print(f"+{border}+") |
| print(f"| {thought.ljust(width - 2)} |") |
| print(f"+{border}+") |
| except UnicodeEncodeError: |
| print(f"\n{label}\n {thought}") |
|
|
|
|
| |
| |
| |
|
|
| DESCRIPTION = """ |
| # 🧠 Sequential Thinking MCP Server |
| |
| Dynamic, reflective problem-solving through structured thoughts. |
| This Gradio app exposes the **Sequential Thinking** tool as an MCP server. |
| |
| **MCP endpoint (SSE):** `http://localhost:7860/gradio_api/mcp/sse` |
| """ |
|
|
| |
| with open('styles.css', 'r') as f: |
| custom_css = f.read() |
|
|
| with gr.Blocks(title="Sequential Thinking MCP", theme=soft_professional_theme, css=custom_css) as demo: |
| gr.Markdown(DESCRIPTION) |
|
|
| with gr.Tabs(): |
|
|
| |
| |
| |
| with gr.Tab("Process Thought"): |
| with gr.Row(equal_height=False): |
| with gr.Column(scale=1): |
| thought_input = gr.Textbox( |
| label="Thought", |
| placeholder="Enter your current thinking step…", |
| lines=5, |
| ) |
| with gr.Row(): |
| thought_number_input = gr.Number( |
| label="Thought Number", value=1, minimum=1, precision=0 |
| ) |
| total_thoughts_input = gr.Number( |
| label="Total Thoughts (estimate)", value=5, minimum=1, precision=0 |
| ) |
| next_thought_needed_input = gr.Checkbox( |
| label="Next Thought Needed", value=True |
| ) |
|
|
| with gr.Accordion("Advanced options", open=False): |
| is_revision_input = gr.Checkbox(label="Is Revision", value=False) |
| revises_thought_input = gr.Number( |
| label="Revises Thought #", |
| minimum=0, |
| precision=0, |
| value=0, |
| info="0 = not applicable", |
| ) |
| branch_from_thought_input = gr.Number( |
| label="Branch From Thought #", |
| minimum=0, |
| precision=0, |
| value=0, |
| info="0 = not applicable", |
| ) |
| branch_id_input = gr.Textbox( |
| label="Branch ID", |
| placeholder="e.g. alternative-approach", |
| ) |
| needs_more_thoughts_input = gr.Checkbox( |
| label="Needs More Thoughts", value=False |
| ) |
|
|
| with gr.Row(): |
| submit_btn = gr.Button("Process Thought", variant="primary") |
| reset_btn = gr.Button("Reset Session", variant="secondary") |
|
|
| with gr.Column(scale=1): |
| output_json = gr.JSON(label="Result") |
| status_box = gr.Textbox(label="Status", interactive=False, visible=True) |
|
|
| submit_btn.click( |
| fn=sequential_thinking, |
| inputs=[ |
| thought_input, |
| thought_number_input, |
| total_thoughts_input, |
| next_thought_needed_input, |
| is_revision_input, |
| revises_thought_input, |
| branch_from_thought_input, |
| branch_id_input, |
| needs_more_thoughts_input, |
| ], |
| outputs=output_json, |
| ) |
|
|
| reset_btn.click(fn=reset_session, inputs=[], outputs=status_box) |
|
|
| |
| |
| |
| with gr.Tab("Thought History"): |
| refresh_btn = gr.Button("Refresh") |
| history_output = gr.JSON(label="All Thoughts") |
| refresh_btn.click(fn=get_history, inputs=[], outputs=history_output) |
|
|
| |
| |
| |
| with gr.Tab("MCP Reference"): |
| gr.Markdown(""" |
| ## Connecting via MCP |
| |
| Add this to your MCP client configuration: |
| |
| ```json |
| { |
| "mcpServers": { |
| "sequential-thinking": { |
| "url": "http://localhost:7860/gradio_api/mcp/sse" |
| } |
| } |
| } |
| ``` |
| |
| ## Exposed MCP Tools |
| |
| | Tool | Description | |
| |------|-------------| |
| | `sequential_thinking` | Process a single thought step | |
| | `reset_session` | Clear history and branches | |
| | `get_history` | Return the full thought history | |
| |
| ## Parameters for `sequential_thinking` |
| |
| | Parameter | Type | Required | Description | |
| |-----------|------|----------|-------------| |
| | `thought` | string | ✅ | Current thinking step | |
| | `thought_number` | int | ✅ | Step index (starts at 1) | |
| | `total_thoughts` | int | ✅ | Estimated total steps | |
| | `next_thought_needed` | bool | ✅ | More steps required? | |
| | `is_revision` | bool | | Revises an earlier thought | |
| | `revises_thought` | int | | Which thought is revised | |
| | `branch_from_thought` | int | | Branch origin thought | |
| | `branch_id` | string | | Branch label | |
| | `needs_more_thoughts` | bool | | Extend beyond estimate | |
| |
| ## Environment Variables |
| |
| | Variable | Default | Description | |
| |----------|---------|-------------| |
| | `DISABLE_THOUGHT_LOGGING` | `false` | Set to `true` to suppress console output | |
| """) |
|
|
|
|
| if __name__ == "__main__": |
| try: |
| demo.launch(mcp_server=True) |
| finally: |
| shutdown_logger() |
|
|